Bootstrap

vue3 uniapp封装一个瀑布流组件


新增组件m-waterfall   这样就可以在页面直接使用 不用在引入了
<template>
	<view class="m-waterfall">
		<view id="m-left-column" class="m-column">
			<slot name="left" :leftList="leftList"></slot>
		</view>
		<view id="m-right-column" class="m-column">
			<slot name="right" :rightList="rightList"></slot>
		</view>

	</view>
</template>

<script setup>
/**
 * @param value  瀑布流数据
 * @param addTime 插入数据的时间间隔
 * @param keyIdData / id值,用于清除某一条数据时,根据此idKey名称找到并移除
 */

import {
		computed,
		defineProps,
		toRefs
	} from "vue"

	
	const props=defineProps({
		// 瀑布流数据
		value: {
			 required: true,
			type: Array,
			default: ()=>[]
		},
		// 每次向结构插入数据的时间间隔,间隔越长,越能保证两列高度相近,但是对用户体验越不好
		addTime: {
			type: [Number, String],
			default: 200
		},
		// id值,用于清除某一条数据时,根据此idKey名称找到并移除
		keyIdData: {
			type: String,
			default: 'id'
		}
	})
	const {
		value,
		addTime,
		keyIdData
	} = toRefs(props)
	const leftList = ref([])
	const rightList = ref([])
	const tempList = ref([])
	const pendingImages = ref(new Map()) // 用于追踪待加载的图片
	const copyFlowList= computed (()=> {
		return cloneData(value.value);
	})
// 记录正在加载的图片数量
	const loadingCount = ref(0)
	// 处理图片加载完成事件
	const preloadImage = async (item) => {
	  return new Promise((resolve) => {
	      if (!item.image) {
	        resolve(item)
	        return
	      }
	  
	      // 如果这个图片已经在加载中,返回现有的 promise
	      if (pendingImages.value.has(item.image)) {
	        return pendingImages.value.get(item.image)
	      }
	  
	      const promise = new Promise((resolveImage) => {
	        uni.getImageInfo({
	          src: item.image,
	          success: (res) => {
	            // 保存图片的实际尺寸信息到 item
	            item.imageWidth = res.width
	            item.imageHeight = res.height
	            pendingImages.value.delete(item.image)
	            resolveImage(item)
	          },
	          fail: () => {
	            pendingImages.value.delete(item.image)
	            resolveImage(item)
	          }
	        })
	      })
	  
	      pendingImages.value.set(item.image, promise)
	      resolve(promise)
	    })
	}
	// const emit=defineEmits(['handleImageLoad'])
	watch(()=>value.value,(nVal,oVal)=>{
		let startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0;
		
		tempList.value = tempList.value.concat(cloneData(nVal.slice(startIndex)));
		splitData();
	
	})
	onMounted(()=>{
	
		tempList.value = cloneData(copyFlowList.value);
		setTimeout(()=>{
			splitData();
		},200)
	})
	
const instance = getCurrentInstance()

	
	const getColumnHeight = (columnId) => {
	  return new Promise((resolve) => {
	    uni.createSelectorQuery()
	      .in(instance)
	      .select(columnId)
	      .boundingClientRect(data => {
	        resolve(data ? data.height : 0)
	      })
	      .exec()
	  })
	}
	const splitData = async () => {
	
		if (tempList.value.length === 0) return
		
		  const item = tempList.value[0]
		  if (!item) return
		
		  try {
		    // 等待图片预加载完成
		    await preloadImage(item)
		
		    // 获取两列的高度
		    const [leftHeight, rightHeight] = await Promise.all([
		      getColumnHeight('#m-left-column'),
		      getColumnHeight('#m-right-column')
		    ])
		
		    // 根据高度决定放入哪一列
		    if (leftHeight <= rightHeight) {
		      leftList.value.push(item)
		    } else {
		      rightList.value.push(item)
		    }
		
		    // 移除已处理的项目
		    tempList.value.splice(0, 1)
		
		    // 继续处理下一个项目
		    if (tempList.value.length) {
		      setTimeout(() => {
		        splitData()
		      }, Number(props.addTime))
		    }
		  } catch (error) {
		    console.error('Error in splitData:', error)
		    // 发生错误时也移除当前项目,继续处理下一个
		    tempList.value.splice(0, 1)
		    if (tempList.value.length) {
		      splitData()
		    }
		  }

	}
	// 复制而不是引用对象和数组
	const cloneData=(data)=>{
		if(data){
			return JSON.parse(JSON.stringify(data));
		}else{
			return [];
		}
		
	}
	
</script>

<style lang="scss" scoped>

.m-waterfall {
	margin-top: 20rpx;
	display: flex;
	flex-direction: row;
	align-items: flex-start;
	gap: 10rpx;
}
 
.m-column {
	display: flex;
	flex: 1;
	flex-direction: column;
	height: auto;
}
 
.m-image {
	width: 100%;

}
</style>

组件使用

<m-waterfall :value="product">
				<!-- 左边数据 -->
				<template v-slot:left="{leftList}">
					<view @click="addDta" class="prodecutitem" v-for="(item,index) in leftList" :key="index">
						<view style="width: 100%;">
							<m-imgage :url="item.image"></m-imgage>
						</view>
						<view class="title">{{item.title}}</view>
						<view class="desc">{{item.title}}</view>
					</view>
				</template>
				<!-- 右边数据 -->
				<template v-slot:right="{rightList}">
					<view class="prodecutitem" v-for="(item,index) in rightList" :key="index">
						<view>
							<m-imgage :url="item.image"></m-imgage>
						</view>
						<view class="title">{{item.title}}</view>
						<view class="desc">{{item.title}}</view>
					</view>
				</template>
			</m-waterfal>

数据
	const product=ref([
		{
			title:'水果蔬菜1',
			image:imgSrc.value
		},
		{
			title:'水果蔬菜2',
			image:"https://img2.baidu.com/it/u=3893165480,918722033&fm=253&fmt=auto&app=120&f=JPEG?w=729&h=1215"
		},
		{
			title:'水果蔬菜3',
			image:imgSrc.value
		},
		{
			title:'水果蔬菜1',
			image:imgSrc.value
		},
		{
			title:'水果蔬菜3',
			image:imgSrc.value
		}
	])
;