核心内容摘要
论文与代码双优化:8款AI工具助力毕业设计高效完成
该源码来自 uviewpro 地址为https://uviewpro.cn/zh/components/waterfall.html我改成vue2的写法 优化了计时器瀑布流插件templatedivclasswaterfulldivclassleftrefleftRefslot nameleft:leftListleftData/slot/divdivclassrightrefrightRefslot nameright:rightListrightData/slot/div/div/templatescript/** * * author COOBY * since
14:19 */exportdefault{props:{list:{type:Array,default:()[]},addTime:{type:Number,default:200}},data(){return{timer:null,leftData:[],rightData:[],tempList:[],}},computed:{copyFlowList(){returnthis.deepClone(this.list);}},watch:{copyFlowList(nVal,oVal){conststartIndexArray.isArray(oVal)oVal.length0?oVal.length:0;// 拼接上原有数据this.tempListthis.tempList.concat(this.deepClone(nVal.slice(startIndex)));this.splitData();},immediate:true},mounted(){this.tempListthis.deepClone(this.copyFlowList);this.splitData();},beforeDestroy(){if(this.timer)clearTimeout(this.timer);this.timernull;},methods:{asyncsplitData(){if(!this.tempList.length)return;if(!this.$refs.leftRef||!this.$refs.rightRef)return;awaitthis.$nextTick();constleftRectthis.$refs.leftRef.offsetHeight;constrightRectthis.$refs.rightRef.offsetHeight;// 如果左边小于或等于右边就添加到左边否则添加到右边constitemthis.tempList[0];// 解决多次快速上拉后可能数据会乱的问题因为经过上面的两个await节点查询阻塞一定时间加上后面的定时器干扰// 数组可能变成[]导致此item值可能为undefinedif(!item)return;if(leftRectrightRect){this.leftData.push(item);}elseif(leftRectrightRect){this.rightData.push(item);}else{// 这里是为了保证第一和第二张添加时左右都能有内容// 因为添加第一张实际队列的高度可能还是0这时需要根据队列元素长度判断下一个该放哪边if(this.leftData.lengththis.rightData.length){this.leftData.push(item);}else{this.rightData.push(item);}}// 移除临时列表的第一项this.tempList.shift();// 如果临时数组还有数据继续循环awaitthis.$nextTick();if(this.tempList.length){if(this.timer)clearTimeout(this.timer);this.timersetTimeout((){this.splitData();},Math.max(0,this.addTime));// 防止负数}},deepClone(obj,cachenewWeakMap()){if(objnull||typeofobj!object)returnobj;if(cache.has(obj))returncache.get(obj);letclone;if(objinstanceofDate){clonenewDate(obj.getTime());}elseif(objinstanceofRegExp){clonenewRegExp(obj);}elseif(objinstanceofMap){clonenewMap(Array.from(obj,([key,value])[key,this.deepClone(value,cache)]));}elseif(objinstanceofSet){clonenewSet(Array.from(obj,valuethis.deepClone(value,cache)));}elseif(Array.isArray(obj)){cloneobj.map(valuethis.deepClone(value,cache));}elseif(Object.prototype.toString.call(obj)[object Object]){cloneObject.create(Object.getPrototypeOf(obj));cache.set(obj,clone);for(const[key,value]ofObject.entries(obj)){clone[key]this.deepClone(value,cache);}}else{cloneObject.assign({},obj);}cache.set(obj,clone);returnclone;}},}/scriptstyle langlessscoped.waterfull{display:flex;gap:2vw;width:92vw;margin:0auto;.left,.right{flex:1;background-color:#fff;height:fit-content;}}/stylevue中使用waterfull:listvideoListtemplate slotleftslot-scope{ leftList }div v-for(item, index) in leftList:keyindexitemVideo:itemitem/itemVideo/div/templatetemplate slotrightslot-scope{ rightList }div v-for(item, index) in rightList:keyindexitemVideo:itemitem/itemVideo/div/template/waterfull
需求目标我们要实现一个组件具备以下能力接收一个动态变化的list数组比如通过上拉加载新增数据自动将新项“智能”分配到左列或右列使两列高度尽可能平衡支持自定义每项添加的间隔时间模拟“逐个加载”的动画效果避免因频繁更新导致的数据错乱或性能问题。
整体结构概览template div classwaterfull div classleft refleftRef slot nameleft :leftListleftData/slot /div div classright refrightRef slot nameright :rightListrightData/slot /div /div /template使用slot实现插槽分发父组件可自由定义左右列的渲染方式通过ref获取左右容器的真实 DOM 高度用于判断插入位置数据分为leftData和rightData两个数组分别控制左右列内容。
核心逻辑拆解
数据监听与增量处理watch:{copyFlowList(nVal,oVal){conststartIndexArray.isArray(oVal)oVal.length0?oVal.length:0;this.tempListthis.tempList.concat(this.deepClone(nVal.slice(startIndex)));this.splitData();},immediate:true}copyFlowList是对props.list的深拷贝避免直接修改原始数据当list变化时只取新增部分slice(startIndex)避免重复处理已有项新增项先存入tempList临时队列再交由splitData逐步分配。
✅为什么用临时队列因为我们希望“逐个”添加项带时间间隔而不是一次性塞入这样能模拟真实加载过程并防止 DOM 高度计算不准。
智能分配算法splitData这是整个组件的灵魂函数asyncsplitData(){if(!this.tempList.length)return;if(!this.$refs.leftRef||!this.$refs.rightRef)return;awaitthis.$nextTick();// 确保 DOM 已更新constleftRectthis.$refs.leftRef.offsetHeight;constrightRectthis.$refs.rightRef.offsetHeight;constitemthis.tempList[0];if(!item)return;if(leftRectrightRect){this.leftData.push(item);}else{this.rightData.push(item);}this.tempList.shift();// 移除已处理项awaitthis.$nextTick();// 等待新项渲染完成高度更新if(this.tempList.length){if(this.timer)clearTimeout(this.timer);this.timersetTimeout((){this.splitData();},Math.max(0,this.addTime));}}分配策略详解条件行为leftHeight rightHeight新项放入左边leftHeight rightHeight新项放入右边为什么不是严格而是这样能确保第一项优先放入左边第二项若高度相同都为0则进入else分支中的兜底逻辑。
兜底逻辑初始状态处理// 当左右高度相等如初始都为0时if(this.leftData.lengththis.rightData.length){this.leftData.push(item);}else{this.rightData.push(item);}防止前两项都塞到同一侧保证左右列都能有内容提升首屏体验。
异步调度与防抖每次添加一项后等待 DOM 渲染完成$nextTick()再计算下一次高度使用setTimeout控制添加频率addTime默认 200ms每次调用前clearTimeout防止多个定时器堆积尤其在快速上拉加载时。
⚠️注意如果不加$nextTick()offsetHeight可能还是旧值导致分配错误