Skip to content

Waterfall 瀑布流

13:10

这是一个瀑布流形式的组件,内容分为左右两列,结合uView的懒加载组件效果更佳。
相较于某些只是奇偶数左右分别,或者没有利用vue作用域插槽的做法,uView Pro的瀑布流实现了真正的 组件化,搭配LazyLoad 懒加载loadMore 加载更多组件,让您开箱即用,眼前一亮。

提示

由于右侧的演示是通过iframe标签引入的,缺少了手机端运行的相关API,或者因为演示区域太小,或者电脑分别率不够高 ,导致演示可能会有问题,手机端有不会这些问题,请在右上角的"演示"中用手机扫码查看对应的效果。

注意

  1. 在微信小程序中,需要hx2.8.11才支持在懒加载中结合其他组件
  2. 新增清空列表和移除某条数据的组件方法,原flow-list参数,需要改为v-model接收传值
  3. 由于hx的问题,支付宝小程序需要hx2.8.2版本及以上才支持本组件

平台差异说明

AppH5微信小程序支付宝小程序百度小程序头条小程序QQ小程序

基本使用

本组件利用了vue的作用域插槽(详见vue文档)特性, 将传入waterfall内部的数据,通过slot(作用域插槽)让用户能在父组件中引用和配置对应的数据,这样做的 原因是可以让用户自定义列表item的结构和样式,达到真正的组件化。

需要注意的是,组件内部导出的数据,需要使用template元素接收,同时通过v-slot指定左右两列的slot,如 v-slot:left="list",这里的list变量名为用户自定义的(意味着您可以起名叫data都是没问题的),它为一个对象,它内部分别有leftListrightList,用于 渲染左右两列的数据,见如下完整示例:

核心代码

html
<u-waterfall v-model="flowList">
	<template v-slot:left="{leftList}">
		<view v-for="(item, index) in leftList" :key="index">
			<!-- 这里编写您的内容,item为您传递给v-model的数组元素 -->
		</view>
	</template>
	<template v-slot:right="{rightList}">
		<view v-for="(item, index) in rightList" :key="index">
			<!-- 这里编写您的内容,item为您传递给v-model的数组元素 -->
		</view>
	</template>
</u-waterfall>

移除或清空数据

移除或者清空,都需要通过ref调用组件内部的方法。

  1. 移除数据

组件内部方法名为remove,需要传入"id"值,这个"id"键值的名称配置参数为idKey(默认id),如下:

假设您的数据为:

js
let arr = [
	{idx: 1, name: 'lisa'},
	{idx: 2, name: 'mary'}
]

那么您应该配置idKeyidx

  1. 清空数据

通过ref手动调用组件内部的clear方法,可以清空左右两列的数据。

说明:具体实现方法,请见下方的示例代码

完整应用示例

html
<template>
	<view class="wrap">
		<u-button @click="clear">清空列表</u-button>
		<u-waterfall v-model="flowList" ref="uWaterfallRef">
			<template v-slot:left="{leftList}">
				<view class="demo-warter" v-for="(item, index) in leftList" :key="index">
					<!-- 警告:微信小程序中需要hx2.8.11版本才支持在template中结合其他组件,比如下方的lazy-load组件 -->
					<u-lazy-load threshold="-450" border-radius="10" :image="item.image" :index="index"></u-lazy-load>
					<view class="demo-title">
						{{item.title}}
					</view>
					<view class="demo-price">
						{{item.price}}元
					</view>
					<view class="demo-tag">
						<view class="demo-tag-owner">
							自营
						</view>
						<view class="demo-tag-text">
							放心购
						</view>
					</view>
					<view class="demo-shop">
						{{item.shop}}
					</view>
					<u-icon name="close-circle-fill" color="#fa3534" size="34" class="u-close" @click="remove(item.id)"></u-icon>
				</view>
			</template>
			<template v-slot:right="{rightList}">
				<view class="demo-warter" v-for="(item, index) in rightList" :key="index">
					<u-lazy-load threshold="-450" border-radius="10" :image="item.image" :index="index"></u-lazy-load>
					<view class="demo-title">
						{{item.title}}
					</view>
					<view class="demo-price">
						{{item.price}}元
					</view>
					<view class="demo-tag">
						<view class="demo-tag-owner">
							自营
						</view>
						<view class="demo-tag-text">
							放心购
						</view>
					</view>
					<view class="demo-shop">
						{{item.shop}}
					</view>
					<u-icon name="close-circle-fill" color="#fa3534" size="34" class="u-close" @click="remove(item.id)"></u-icon>
				</view>
			</template>
		</u-waterfall>
		<u-loadmore bg-color="rgb(240, 240, 240)" :status="loadStatus" @loadmore="addRandomData"></u-loadmore>
	</view>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { onLoad, onReachBottom } from '@dcloudio/uni-app'

const loadStatus = ref('loadmore')
const flowList = ref<any[]>([])
const uWaterfallRef = ref()
const list = [
  {
    price: 35,
    title: '北国风光,千里冰封,万里雪飘',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic.sc.chinaz.com/Files/pic/pic9/202002/zzpic23327_s.jpg',
  },
  {
    price: 75,
    title: '望长城内外,惟余莽莽',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic.sc.chinaz.com/Files/pic/pic9/202002/zzpic23325_s.jpg',
  },
  {
    price: 385,
    title: '大河上下,顿失滔滔',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic2.sc.chinaz.com/Files/pic/pic9/202002/hpic2119_s.jpg',
  },
  {
    price: 784,
    title: '欲与天公试比高',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic2.sc.chinaz.com/Files/pic/pic9/202002/zzpic23369_s.jpg',
  },
  {
    price: 7891,
    title: '须晴日,看红装素裹,分外妖娆',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic2.sc.chinaz.com/Files/pic/pic9/202002/hpic2130_s.jpg',
  },
  {
    price: 2341,
    shop: '李白杜甫白居易旗舰店',
    title: '江山如此多娇,引无数英雄竞折腰',
    image: 'http://pic1.sc.chinaz.com/Files/pic/pic9/202002/zzpic23346_s.jpg',
  },
  {
    price: 661,
    shop: '李白杜甫白居易旗舰店',
    title: '惜秦皇汉武,略输文采',
    image: 'http://pic1.sc.chinaz.com/Files/pic/pic9/202002/zzpic23344_s.jpg',
  },
  {
    price: 1654,
    title: '唐宗宋祖,稍逊风骚',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic1.sc.chinaz.com/Files/pic/pic9/202002/zzpic23343_s.jpg',
  },
  {
    price: 1678,
    title: '一代天骄,成吉思汗',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic1.sc.chinaz.com/Files/pic/pic9/202002/zzpic23343_s.jpg',
  },
  {
    price: 924,
    title: '只识弯弓射大雕',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic1.sc.chinaz.com/Files/pic/pic9/202002/zzpic23343_s.jpg',
  },
  {
    price: 8243,
    title: '俱往矣,数风流人物,还看今朝',
    shop: '李白杜甫白居易旗舰店',
    image: 'http://pic1.sc.chinaz.com/Files/pic/pic9/202002/zzpic23343_s.jpg',
  },
]

function addRandomData() {
  for(let i = 0; i < 10; i++) {
    let index = uni.$u.random(0, list.length - 1)
    // 先转成字符串再转成对象,避免数组对象引用导致数据混乱
    let item = JSON.parse(JSON.stringify(list[index]))
    item.id = uni.$u.guid()
    flowList.value.push(item)
  }
}

function remove(id: string) {
  uWaterfallRef.value?.remove(id)
}

function clear() {
  uWaterfallRef.value?.clear()
}

onLoad(() => {
  addRandomData()
})

onReachBottom(() => {
  loadStatus.value = 'loading'
  setTimeout(() => {
    addRandomData()
    loadStatus.value = 'loadmore'
  }, 1000)
})
</script>

<style>
	/* page不能写带scope的style标签中,否则无效 */
	page {
		background-color: rgb(240, 240, 240);
	}
</style>

<style lang="scss" scoped>
	.demo-warter {
		border-radius: 8px;
		margin: 5px;
		background-color: #ffffff;
		padding: 8px;
		position: relative;
	}
	
	.u-close {
		position: absolute;
		top: 32rpx;
		right: 32rpx;
	}
	
	.demo-image {
		width: 100%;
		border-radius: 4px;
	}
	
	.demo-title {
		font-size: 30rpx;
		margin-top: 5px;
		color: $u-main-color;
	}
	
	.demo-tag {
		display: flex;
		margin-top: 5px;
	}
	
	.demo-tag-owner {
		background-color: $u-type-error;
		color: #FFFFFF;
		display: flex;
		align-items: center;
		padding: 4rpx 14rpx;
		border-radius: 50rpx;
		font-size: 20rpx;
		line-height: 1;
	}
	
	.demo-tag-text {
		border: 1px solid $u-type-primary;
		color: $u-type-primary;
		margin-left: 10px;
		border-radius: 50rpx;
		line-height: 1;
		padding: 4rpx 14rpx;
		display: flex;
		align-items: center;
		border-radius: 50rpx;
		font-size: 20rpx;
	}
	
	.demo-price {
		font-size: 30rpx;
		color: $u-type-error;
		margin-top: 5px;
	}
	
	.demo-shop {
		font-size: 22rpx;
		color: $u-tips-color;
		margin-top: 5px;
	}
</style>

注意事项

  1. 上方的示例中,结合了uView的lazyload懒加载loadmore加载更多组件,具体用法,请见文档。
  2. 需要通过v-model传递参数,将数据传递给组件,组件内部将每次新增的数据,通过动态查询左右列的高度 添加到高度低的一列。
  3. 组件有一个add-time参数,用于将单条数据添加到队列的时间间隔,因为图片加载是需要时间的,所以瀑布流左右列 的高度会不定时改变,add-time值越大,对程序效果越好,但是对用户来说,越大值可能就是以能感受的速度一个一个添加 到队列尾部的,所以这是一个双面性的结果。
  4. 由于图片加载完成的时机是不确定的,导致图片加载完成时,队列的高度会发生改变,而且这个时机是无法确定的, 所以每次添加数据的时候,单次请求的所有数据中最后一个数据不一定能准确添加高度更低的队列一侧,但是可以保证下一次请求数据的第一条 能准确添加到队列高度低的一侧。

API

IndexBar Props

注意:通过v-model双向绑定传递参数,因为组件内部需要修改父组件的值。

参数说明类型默认值可选值
add-time单条数据添加到队列的时间间隔,单位ms,见上方注意事项说明String | Number200-
idKey数据的唯一值的键名,见上方说明Stringid-

Methods

这些为组件内部的方法,需要通过ref调用

参数说明
clear清空列表数据
remove(id)id为唯一的"id"值,见上方说明