tencent.jquery.pix.component 1.0.6-6.beta1

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,819 @@
1
+ import "./waterfall.scss"
2
+ import { getEnv } from "../config.js";
3
+ import { remToPx } from "../../utils/utils.js";
4
+
5
+ let $ = null;
6
+
7
+ // 默认配置
8
+ const DEFAULTS = {
9
+ columns: 2, // 列数
10
+ columnGap: 10, // 左右间隔
11
+ rowGap: 12, // 上下间隔
12
+ marginTop: 0, // 距离顶部距离
13
+ marginBottom: 12, // 最后一行距离底部距离
14
+ bufferHeight: '2rem', // 缓冲高度,进行可能的预先添加高度
15
+ data: [], // 数据源
16
+ container: '', // 容器元素
17
+ renderItem(data, index) { // 元素首次渲染时的回调函数, 如果把updateItem设置为空,那么更新时则会兜底触发renderItem
18
+ return '<div class="waterfall-item"></div>';
19
+ },
20
+ scrollDom: null, // 滚动元素,如果传入了滚动元素,那么用来计算的窗口高度就以滚动元素的高度为准
21
+ // 传入 $node, data, index
22
+ updateItem: null, // 元素更新时的回调函数
23
+ onscroll: null, // 滚动事件回调函数
24
+ shouldOccupySpace: null, // 是否是静态数据的回调函数,静态数据能够占用元素
25
+ showLoading: null, // 展示loading的回调函数 params:$node
26
+ hideLoading: null, // 隐藏loading的回调函数
27
+ createLoading: null, // 创建loading的回调函数
28
+ };
29
+
30
+ export function Waterfall(optionsInput = {}) {
31
+ $ = getEnv().$;
32
+
33
+ this.options = Object.assign({}, DEFAULTS, optionsInput);
34
+ const options = this.options;
35
+ const $container = $(options.container);
36
+ // 标记是否有更新元素用的回调函数
37
+ this.hasUpdateItem = options.updateItem && (options.updateItem.constructor === Function) ? true : false;
38
+
39
+ // 新方案:数据ID映射机制
40
+ this.dataIdMap = new Map(); // 数据ID -> 布局信息映射
41
+ this.nextDataId = 0; // 下一个数据ID
42
+ this.renderedDataIds = new Set(); // 已渲染的数据ID集合
43
+
44
+ this.$loadingNode = null;
45
+
46
+ this.isShowLoading = false; // 是否展示loading
47
+
48
+
49
+ // 间隔字符串转数字
50
+ if (options.columnGap.constructor === String) {
51
+ // 如果是rem单位,则需要计算
52
+ if (options.columnGap.indexOf('rem') > -1) {
53
+ options.columnGap = remToPx(options.columnGap);
54
+ } else {
55
+ options.columnGap = parseFloat(options.columnGap);
56
+ }
57
+ }
58
+ if (options.rowGap.constructor === String) {
59
+ // 如果是rem单位,则需要计算
60
+ if (options.rowGap.indexOf('rem') > -1) {
61
+ options.rowGap = remToPx(options.rowGap);
62
+ } else {
63
+ options.rowGap = parseFloat(options.rowGap);
64
+ }
65
+ }
66
+ if (options.bufferHeight.constructor === String) {
67
+ // 如果是rem单位,则需要计算
68
+ if (options.bufferHeight.indexOf('rem') > -1) {
69
+ options.bufferHeight = remToPx(options.bufferHeight);
70
+ } else {
71
+ options.bufferHeight = parseFloat(options.bufferHeight);
72
+ }
73
+ }
74
+ if (options.marginTop.constructor === String) {
75
+ // 如果是rem单位,则需要计算
76
+ if (options.marginTop.indexOf('rem') > -1) {
77
+ options.marginTop = remToPx(options.marginTop);
78
+ } else {
79
+ options.marginTop = parseFloat(options.marginTop);
80
+ }
81
+ }
82
+ if (options.marginBottom.constructor === String) {
83
+ // 如果是rem单位,则需要计算
84
+ if (options.marginBottom.indexOf('rem') > -1) {
85
+ options.marginBottom = remToPx(options.marginBottom);
86
+ } else {
87
+ options.marginBottom = parseFloat(options.marginBottom);
88
+ }
89
+ }
90
+
91
+ const allWidth = $container.width();
92
+ this.allWidth = allWidth;
93
+ // 计算每列的宽度
94
+ this.columnWidth = (allWidth - (options.columns - 1) * options.columnGap) / options.columns;
95
+
96
+ // 记录列数
97
+ this.columns = options.columns;
98
+
99
+ this.columnItems = []; // 列元素数组
100
+ for (let i = 0; i < this.columns; i++) {
101
+ this.columnItems.push(this.createColumn(i));
102
+ }
103
+
104
+ this.renderIndex = 0; // 渲染索引(保留兼容性)
105
+ this.activeNodes = new Map(); // 当前活跃节点(数据ID -> DOM)
106
+ this.nodePool = []; // DOM 节点池
107
+
108
+ // 新方案:初始化数据ID映射
109
+ this.dataIdMap = new Map(); // 数据ID -> 布局信息映射
110
+ this.nextDataId = 0; // 下一个数据ID
111
+ this.renderedDataIds = new Set(); // 已渲染的数据ID集合
112
+
113
+ // 为初始数据分配数据ID
114
+ this.options.data.forEach((item, index) => {
115
+ const dataId = index; //this.nextDataId++;
116
+ this.dataIdMap.set(dataId, {
117
+ data: true,//item,
118
+ originalIndex: index,
119
+ layoutInfo: null // 将在布局时填充
120
+ });
121
+ });
122
+
123
+ this.init();
124
+ }
125
+
126
+
127
+ Waterfall.prototype.init = function () {
128
+ const self = this;
129
+ const options = this.options;
130
+ const $container = $(options.container);
131
+ const $scrollDom = options.scrollDom ? $(options.scrollDom) : $container;
132
+
133
+ this.nodePool = []; // DOM 节点池
134
+ this.activeNodes = new Map(); // 当前活跃节点(索引 -> DOM)
135
+ this.allReadyNodes = new Map(); // 所有节点(索引 -> DOM)
136
+ this.renderIndex = 0; // 渲染索引(保留兼容性)
137
+
138
+ $container.html(`
139
+ <div class="waterfall-list-scroll" style="">
140
+ <div class="waterfall-list-viewport"></div>
141
+ </div>
142
+ `);
143
+
144
+
145
+
146
+ // 如果有定义loading函数 那么创建一个loading节点元素
147
+ console.log('options.createLoading', options.createLoading)
148
+ if (options.createLoading) {
149
+ this.$loadingNode = $(
150
+ `<div class="waterfall-loading" style="transform: translate(0px, -99999px)"></div>`
151
+ );
152
+ $container.find('.waterfall-list-viewport').append(this.$loadingNode);
153
+
154
+ options.createLoading(this.$loadingNode);
155
+ }
156
+
157
+ // 绑定滚动事件(节流处理)
158
+ $scrollDom.off().on('scroll', function () {
159
+ self.scrollTop = $(this).scrollTop();
160
+
161
+ window.requestAnimationFrame(() => {
162
+ self.updateVisibleItems();
163
+ });
164
+
165
+ if (options.onscroll && options.onscroll.constructor === Function) {
166
+ options.onscroll(this, self.scrollTop);
167
+ }
168
+ });
169
+
170
+ this.scrollTop = $scrollDom.scrollTop(); // 当前滚动位置
171
+
172
+ // 首次渲染
173
+ self.updateVisibleItems();
174
+
175
+ }
176
+
177
+ // force 强制更新渲染
178
+ Waterfall.prototype.updateVisibleItems = function (force = false) {
179
+ const self = this;
180
+ const options = this.options;
181
+ let h = 0;
182
+ if (options.scrollDom) {
183
+ h = $(options.scrollDom).height();
184
+ } else {
185
+ h = $(options.container).height();
186
+ }
187
+
188
+ const startTop = self.scrollTop; // 当前滚动位置
189
+ const endTop = startTop + h;
190
+ // console.log('startTop', startTop)
191
+ console.log('endTop', endTop)
192
+
193
+ // 进行可见区域的渲染更新
194
+ this.updateCardsInView({
195
+ start: startTop,
196
+ end: endTop,
197
+ force
198
+ });
199
+
200
+ }
201
+
202
+ // 新增卡片
203
+ Waterfall.prototype.appendCard = function (data, dataId, { top, left }) {
204
+ const self = this;
205
+ const options = this.options;
206
+ const $container = $(options.container);
207
+ const $viewport = $container.find('.waterfall-list-viewport');
208
+
209
+ // 新方案:基于数据ID的数据验证
210
+ if (!this.dataIdMap.has(dataId)) {
211
+ console.error('Waterfall: Invalid dataId in appendCard', dataId);
212
+ return null;
213
+ }
214
+
215
+ const dataInfo = this.dataIdMap.get(dataId);
216
+ if (!dataInfo || !dataInfo.data) {
217
+ console.warn('Waterfall: Empty data for dataId', dataId);
218
+ }
219
+
220
+
221
+ const $card = $(
222
+ `<div class="waterfall-item"
223
+ data-index="${dataId}"
224
+ style="position: absolute;transform:translate(${left}px,${top}px);"
225
+ >
226
+ ${options.renderItem(data, dataInfo.originalIndex)}
227
+ </div> `
228
+ );
229
+
230
+ this.renderedDataIds.add(dataId);
231
+
232
+ if (options.columns !== 1) {
233
+ $card.width(this.columnWidth + 'px');
234
+ }
235
+
236
+
237
+ $viewport.append($card);
238
+ return $card;
239
+ }
240
+
241
+ // 获取指定高度下的卡片索引
242
+ Waterfall.prototype.updateCardsInView = async function ({ start, end, force = false }) {
243
+ const options = this.options;
244
+ const minHeight = this.getMinHeight();
245
+ const endBuffer = end + options.bufferHeight;
246
+ if (minHeight < endBuffer) {
247
+ // 如果不够 进行补建
248
+ await this.createCards({ end: endBuffer });
249
+ }
250
+
251
+ const startNum = start - options.bufferHeight;
252
+ const endNum = end + options.bufferHeight;
253
+ // 新方案:基于数据ID映射机制
254
+ const newActiveNodes = new Map();
255
+ for (let i = 0; i < this.columns; i++) {
256
+ const column = this.columnItems[i];
257
+
258
+ for (let j = 0; j < column.children.length; j++) {
259
+ const row = column.children[j];
260
+ const dataId = row.dataId; // 使用dataId替代renderIndex
261
+
262
+ // 验证数据ID有效性
263
+ if (!this.dataIdMap.has(dataId)) {
264
+ console.warn('Waterfall: Invalid dataId detected', dataId);
265
+ continue;
266
+ }
267
+
268
+ const dataInfo = this.dataIdMap.get(dataId);
269
+
270
+ if (!dataInfo) {
271
+ console.warn('Waterfall: Invalid data for dataId', dataId);
272
+ continue;
273
+ }
274
+
275
+ const data = options.data[dataId]
276
+
277
+ // 如果当前这个节点是特殊节点 不用做处理
278
+ // 如果是特殊的静态占用元素卡片,需要指定节点不变更的数据,那么该数据的节点不能被其他数据使用
279
+ let specialNode = false;
280
+ if (options.shouldOccupySpace) {
281
+ specialNode = options.shouldOccupySpace(data) || false;
282
+ }
283
+ if (specialNode) {
284
+ continue;
285
+ }
286
+
287
+ // 在可视区域内 进行有关卡片的操作
288
+ const bool = row.top <= endNum && row.bottom >= startNum;
289
+ if (bool) {
290
+ // 理论上什么都不动,因为卡片的位置不会变
291
+ const $card = this.activeNodes.get(dataId);
292
+
293
+ let $node = null;
294
+
295
+ // 遍历当前的节点是否被占用,如果被占用的话,就得要从nodePool中取一个
296
+ let bool = true;
297
+ if ($card) {
298
+ bool = hasNodeInActives(newActiveNodes, $card)
299
+ }
300
+ // 如果卡片已经在DOM中,则不用更新位置
301
+ if (bool === false) {
302
+ $node = $card;
303
+ this.activeNodes.delete(dataId);
304
+ // 如果是强更,这里才会采取更新
305
+ if (force) {
306
+ this.updateRenderUI($node, data, dataId);
307
+ }
308
+
309
+ } else {
310
+ const $card = this.allReadyNodes.get(dataId);
311
+ // 遍历当前的节点是否被占用,如果被占用的话,就得要从nodePool中取一个
312
+ let bool = true;
313
+ if ($card) {
314
+ bool = hasNodeInActives(newActiveNodes, $card)
315
+ }
316
+ if (bool === false) {
317
+ // 如果成功获取到card并没有占用 那么就复用这个card
318
+ $node = $card;
319
+
320
+ this.updateRenderUI($node, data, dataId);
321
+
322
+ } else {
323
+ // 卡片不在DOM中,则更新位置
324
+ $node = getNodePoolPop(this.nodePool, newActiveNodes);
325
+ if ($node === null) {
326
+ // 这里是往上方拖动时,可能需要补建的情况
327
+ $node = this.appendCard(data, dataId, {
328
+ top: row.top, left: row.left
329
+ });
330
+ row.$node = $node;
331
+ } else {
332
+ this.updateRenderUI($node, data, dataId);
333
+ row.$node = $node;
334
+ }
335
+ }
336
+ }
337
+ $node.css({
338
+ 'transform': `translate(${row.left}px,${row.top}px)`,
339
+ }).attr('data-index', dataId);
340
+
341
+ newActiveNodes.set(dataId, $node);
342
+
343
+ // 清除掉在NodePool中的card
344
+ const index = this.nodePool.indexOf($card);
345
+ if (index !== -1) {
346
+ this.nodePool.splice(index, 1);
347
+ }
348
+
349
+ // console.log('302-row', row.dataId, dataInfo);
350
+ }
351
+ }
352
+ }
353
+
354
+ // 阶段2:处理不活跃节点
355
+ this.activeNodes.forEach($node => {
356
+ $node.css('transform', `translateY(-9999px)`);// 移出可视区域
357
+ if (this.nodePool.indexOf($node) === -1) {
358
+ this.nodePool.push($node);
359
+ }
360
+ });
361
+ this.activeNodes = newActiveNodes;
362
+ // console.log('this.activeNodes', this.activeNodes);
363
+ // console.log('this.nodePool', this.nodePool);
364
+ }
365
+
366
+
367
+ Waterfall.prototype.getMaxHeight = function () {
368
+ let maxHeight = 0;
369
+ for (let i = 0; i < this.columns; i++) {
370
+ const column = this.columnItems[i]
371
+
372
+ // 获取每组元素列表的最后一个bottom值
373
+ maxHeight = Math.max(maxHeight, column.bottom);
374
+
375
+ }
376
+ return maxHeight;
377
+ }
378
+
379
+ Waterfall.prototype.getMinHeight = function () {
380
+ let minHeight = 0;
381
+ for (let i = 0; i < this.columns; i++) {
382
+ const column = this.columnItems[i]
383
+ if (minHeight === 0) {
384
+ minHeight = column.bottom;
385
+ }
386
+ // 获取每组元素列表的最后一个bottom值
387
+ minHeight = Math.min(minHeight, column.bottom);
388
+
389
+ }
390
+ return minHeight;
391
+ }
392
+
393
+ Waterfall.prototype.getMinHeightColumn = function () {
394
+ let minHeight = -1;
395
+ let mimHeightColumn = null;
396
+ for (let i = 0; i < this.columns; i++) {
397
+ const column = this.columnItems[i]
398
+
399
+ // 获取每组元素列表的最后一个bottom值
400
+ if (minHeight > column.bottom) {
401
+ minHeight = column.bottom;
402
+ mimHeightColumn = column;
403
+ } else if (minHeight === -1) {
404
+ minHeight = column.bottom;
405
+ mimHeightColumn = column;
406
+ }
407
+
408
+ }
409
+
410
+ return mimHeightColumn;
411
+ }
412
+
413
+ // 创建卡片
414
+ Waterfall.prototype.createCards = function ({ end, dataId = -1 }, callback) {
415
+ const self = this;
416
+ const options = this.options;
417
+
418
+ return new Promise((resolve) => {
419
+ // 新方案:获取下一个未渲染的数据ID
420
+ let nextDataId = null;
421
+ for (let [dataId, dataInfo] of this.dataIdMap) {
422
+ if (!this.renderedDataIds.has(dataId)) {
423
+ nextDataId = dataId;
424
+ break;
425
+ }
426
+ }
427
+
428
+
429
+ // 如果没有更多数据需要渲染
430
+ if (nextDataId === null) {
431
+ this.setScrollHeight();
432
+ return resolve();
433
+ }
434
+
435
+ const dataInfo = this.dataIdMap.get(nextDataId);
436
+ if (!dataInfo || !dataInfo.data) {
437
+ console.warn('Waterfall: Invalid data for dataId', nextDataId);
438
+ return resolve();
439
+ }
440
+
441
+ if (this.renderIndex >= options.data.length) {
442
+ this.setScrollHeight();
443
+ return resolve();
444
+ }
445
+
446
+ const data = options.data[nextDataId];
447
+
448
+ let column = this.getMinHeightColumn();
449
+ if (column === null) {
450
+ column = this.columnItems[0];
451
+ }
452
+
453
+ const top = column.bottom === 0 ? options.marginTop : (column.bottom + options.rowGap);
454
+ const position = { top, left: column.left };
455
+ const row = createDefaultRow(position);
456
+
457
+ this.renderIndex += 1;
458
+
459
+ let specialNode = false;
460
+
461
+ // 如果是特殊的卡片,需要指定节点不变更的数据,那么该数据的节点不能被其他数据使用
462
+ if (options.shouldOccupySpace) {
463
+ specialNode = options.shouldOccupySpace(data) || false;
464
+ }
465
+
466
+ // 添加卡片,使用dataId作为唯一标识
467
+ let $card = null;
468
+ if (this.nodePool.length === 0 || specialNode === true) {
469
+ $card = this.appendCard(data, nextDataId, position);
470
+ } else {
471
+ const $tmp = getNodePoolPop(this.nodePool, this.activeNodes);
472
+ if ($tmp) {
473
+ $card = $tmp;
474
+ $card.css({
475
+ 'transform': `translate(${row.left}px,${row.top}px)`,
476
+ }).attr('data-index', nextDataId);
477
+ this.updateRenderUI($card, data, nextDataId);
478
+ } else {
479
+ $card = this.appendCard(data, nextDataId, position);
480
+ }
481
+
482
+ }
483
+
484
+ row.$node = $card;
485
+ row.dataId = nextDataId; // 使用dataId替代renderIndex
486
+ if (dataId !== -1) {
487
+ row.dataId = dataId;
488
+ }
489
+
490
+ if (specialNode === false) {
491
+ // 把新增的卡片放进 activeNodes 当成活跃节点元素,那么是 可以动态使用的
492
+ this.activeNodes.set(nextDataId, $card);
493
+
494
+ this.allReadyNodes.set(nextDataId, $card);
495
+ } else {
496
+ // 如果是特殊的,这里不要记录了
497
+ this.allReadyNodes.set(nextDataId, null);
498
+ }
499
+
500
+ this.renderedDataIds.add(nextDataId);
501
+
502
+ setTimeout(() => {
503
+ window.requestAnimationFrame(() => {
504
+ // 更新列的底部距离
505
+ column.bottom = top + $card.height();
506
+ console.log('column.bottom', column.bottom, $card.height());
507
+ column.children.push(row);
508
+ row.bottom = column.bottom;
509
+
510
+ // 检查是否需要继续创建卡片
511
+ const minHeight = this.getMinHeight();
512
+ const hasMoreData = this.renderedDataIds.size < this.dataIdMap.size;
513
+
514
+ if (hasMoreData && (minHeight < end)) {
515
+
516
+ this.createCards({ end }, () => {
517
+ resolve();
518
+ if (callback) {
519
+ callback();
520
+ }
521
+ });
522
+ } else {
523
+ this.setScrollHeight();
524
+ resolve();
525
+ if (callback) {
526
+ callback();
527
+ }
528
+ }
529
+ });
530
+ }, 42);
531
+ });
532
+
533
+ }
534
+
535
+
536
+ Waterfall.prototype.createColumn = function (index) {
537
+ const res = {
538
+ left: 0,
539
+ bottom: 0,
540
+ width: 0,
541
+ children: []
542
+ }
543
+
544
+ const options = this.options;
545
+ const columnWidth = this.columnWidth;
546
+
547
+ res.width = this.columnWidth;
548
+ res.left = (index * (columnWidth + options.columnGap));
549
+ return res;
550
+ }
551
+
552
+ Waterfall.prototype.updateRenderUI = function ($node, data, dataId) {
553
+ const options = this.options;
554
+
555
+ // 新方案:基于数据ID的数据验证
556
+ if (!this.dataIdMap.has(dataId)) {
557
+ console.error('Waterfall: Invalid dataId in updateRenderUI', dataId);
558
+ return;
559
+ }
560
+
561
+ const dataInfo = this.dataIdMap.get(dataId);
562
+ if (!dataInfo || !dataInfo.data) {
563
+ console.warn('Waterfall: Empty data for dataId', dataId);
564
+ }
565
+
566
+ if (this.hasUpdateItem === true) {
567
+ options.updateItem($node, data, dataInfo.originalIndex)
568
+ } else {
569
+ $node.html(options.renderItem(data, dataInfo.originalIndex));
570
+ }
571
+ }
572
+
573
+
574
+
575
+ Waterfall.prototype.updateData = async function (newData) {
576
+ const options = this.options;
577
+ options.data = newData;
578
+
579
+ // 新方案:重新建立数据ID映射
580
+ //this.dataIdMap.clear();
581
+ //this.renderedDataIds.clear();
582
+ //this.nextDataId = 0;
583
+
584
+ // 为每个数据项分配唯一ID
585
+ let bool = true
586
+ let count = options.data.length - 1;
587
+ let index = 0;
588
+
589
+ while (bool) {
590
+ if (index > count) {
591
+ bool = false
592
+ this.updateVisibleItems(true); // 强制更新渲染
593
+ break;
594
+ }
595
+
596
+ const dataId = index;
597
+ this.dataIdMap.set(dataId, {
598
+ data: true,
599
+ originalIndex: dataId,
600
+ layoutInfo: null
601
+ });
602
+
603
+ await this.createCards({ end: 0, dataId })
604
+
605
+ index += 1;
606
+
607
+ }
608
+ // options.data.forEach((item, index) => {
609
+ // const dataId = index; // this.nextDataId++;
610
+ // if (!this.allReadyNodes.has(dataId)) {
611
+ // this.dataIdMap.set(dataId, {
612
+ // data: true,// item,
613
+ // originalIndex: index,
614
+ // layoutInfo: null // 将在布局时填充
615
+ // });
616
+ // // 如果没有准备好这个数据,这里要创建一个占位节点
617
+ // this.createCards({ end: 0, dataId });
618
+ // }
619
+ // });
620
+
621
+ // this.updateVisibleItems(true); // 强制更新渲染
622
+
623
+
624
+ }
625
+
626
+ // 某个数据进行了UI变更,触发高度重新绘制
627
+ Waterfall.prototype.updateCard = function (data) {
628
+ const options = this.options;
629
+ let dataId = -1;
630
+ if (typeof data === 'number') {
631
+ dataId = data;
632
+ } else {
633
+ for (let i = 0; i < options.data.length; i++) {
634
+ if (options.data[i] === data) {
635
+ dataId = i;
636
+ break;
637
+ }
638
+ }
639
+ }
640
+ if (dataId === -1) {
641
+ // 没有匹配到数据,进行退出
642
+ console.log('Waterfall: Invalid data in options.data');
643
+ return
644
+ }
645
+
646
+ // 从activeNodes中获取该数据的节点
647
+ if (!this.activeNodes.has(dataId)) {
648
+ console.log('Waterfall: Invalid dataId in activeNodes');
649
+ return
650
+ }
651
+
652
+ const $node = this.activeNodes.get(dataId);
653
+ const height = $node.height();
654
+
655
+ // 重新计算该数据所在列的卡片位置
656
+ let needUpdate = false
657
+ const columnItems = this.columnItems;
658
+ for (let i = 0; i < columnItems.length; i++) {
659
+ const column = columnItems[i];
660
+ let bool = false
661
+ let minus = 0
662
+ // 这里为了简化,各列的瀑布流保持不动,只更新各列下节点的top位置,不做节点的跨列位移
663
+ for (let j = 0; j < column.children.length; j++) {
664
+ const row = column.children[j];
665
+ if (row.dataId === dataId) {
666
+ bool = true
667
+ const oldHeight = row.bottom - row.top;
668
+
669
+ minus = height - oldHeight;
670
+
671
+ if (minus === 0) {
672
+ // 找到了原节点数据,对比后没有变更 那么直接退出
673
+ return;
674
+ }
675
+ needUpdate = true
676
+ row.bottom += minus;
677
+ } else if (minus !== 0) {
678
+ row.top += minus;
679
+ row.bottom += minus;
680
+ }
681
+ }
682
+ if (bool) {
683
+ column.bottom += minus;
684
+ break;
685
+ }
686
+ }
687
+ if (needUpdate) {
688
+ this.updateVisibleItems(true); // 强制更新渲染
689
+ }
690
+ }
691
+
692
+ // 重新计算设置一遍所有的卡片位置
693
+ Waterfall.prototype.updatePointCards = function () {
694
+ // 有问题 不要用
695
+ return
696
+ const self = this;
697
+ const options = this.options;
698
+ const columnItems = this.columnItems;
699
+ let top = options.marginTop;
700
+ for (let i = 0; i < columnItems.length; i++) {
701
+ const column = columnItems[i];
702
+ // 这里为了简化,各列的瀑布流保持不动,只更新各列下节点的top位置,不做节点的跨列位移
703
+ for (let j = 0; j < column.children.length; j++) {
704
+ const row = column.children[j];
705
+ const $card = row.$node;
706
+
707
+ // 验证数据ID有效性
708
+ if (!this.dataIdMap.has(row.dataId)) {
709
+ console.warn('Waterfall: Invalid dataId in updatePointCards', row.dataId);
710
+ continue;
711
+ }
712
+
713
+ // 第一个的top不需要更新
714
+ if (j === 0) {
715
+ row.bottom = top + $card.height();
716
+ } else {
717
+ row.top = column.children[j - 1].bottom + options.rowGap;
718
+ row.bottom = row.top + $card.height();
719
+ // 更新卡片位置
720
+ // $card.css({
721
+ // 'transform': `translate(${row.left}px,${row.top}px)`,
722
+ // })
723
+ }
724
+
725
+ }
726
+ // 设置一次该列的bottom
727
+ column.bottom = getBottomByColumn(column);
728
+ }
729
+ }
730
+
731
+ // 展示loading的回调函数
732
+ Waterfall.prototype.showLoading = function (callback = null) {
733
+ this.isShowLoading = true;
734
+ const options = this.options;
735
+ let $node = null
736
+ if (this.$loadingNode) {
737
+ let loadingTop = this.getMaxHeight() + options.rowGap
738
+ this.$loadingNode.css('transform', `translate(0px,${loadingTop}px)`);
739
+ $node = this.$loadingNode
740
+ }
741
+
742
+ if (callback) callback($node)
743
+ this.setScrollHeight();
744
+ }
745
+
746
+ // 隐藏loading的回调函数
747
+ Waterfall.prototype.hideLoading = function (callback = null) {
748
+ this.isShowLoading = false;
749
+ const options = this.options;
750
+ let $node = null
751
+ if (this.$loadingNode) {
752
+ let h1 = this.getMaxHeight() + options.marginBottom
753
+ this.$loadingNode.css('transform', `translate(0px,-99999px)`);
754
+ //如果要设置高度,那么这里判断一下当前是否正在做updata 一般这里被调用时,数据已经读到,在updata的同一时间调用了该函数
755
+ // 如果两个时刻高度是一致的 那么数据就是一致的 这里重新设置回来高度即可
756
+ window.requestAnimationFrame(() => {
757
+ this.setScrollHeight();
758
+ })
759
+ $node = this.$loadingNode
760
+ }
761
+
762
+ if (callback) callback($node)
763
+ this.setScrollHeight();
764
+ }
765
+
766
+ // 设置滚动条列表的高度
767
+ Waterfall.prototype.setScrollHeight = function () {
768
+ const options = this.options;
769
+ const $container = $(options.container);
770
+ let h = this.getMaxHeight();
771
+ if (this.isShowLoading === true) {
772
+ if (this.$loadingNode) {
773
+ h += options.rowGap + this.$loadingNode.height();
774
+ }
775
+ }
776
+ h += options.marginBottom;
777
+ $container.find('.waterfall-list-scroll').css('height', h + 'px');
778
+ }
779
+
780
+ function createDefaultRow({ top, left }) {
781
+ return {
782
+ top,
783
+ left,
784
+ bottom: 0,
785
+ $node: null,
786
+ dataId: -1 // 新方案:使用dataId替代renderIndex
787
+ }
788
+ }
789
+
790
+
791
+ function getBottomByColumn(column) {
792
+ if (column.children.length === 0) {
793
+ return 0;
794
+ }
795
+ const child = column.children;
796
+ return child[child.length - 1].bottom;
797
+ }
798
+
799
+ function hasNodeInActives(mapObj, $node) {
800
+ let bool = false
801
+ mapObj.forEach((item) => {
802
+ if (item === $node) {
803
+ bool = true;
804
+ }
805
+ });
806
+ return bool;
807
+ }
808
+
809
+ // 从众多jq对象数组取得一个不重复的
810
+ function getNodePoolPop(nodePool, actives) {
811
+ for (let i = 0; i < nodePool.length; i++) {
812
+ const $node = nodePool[i];
813
+ if (!hasNodeInActives(actives, $node)) {
814
+ nodePool.splice(i, 1);
815
+ return $node;
816
+ }
817
+ }
818
+ return null;
819
+ }