Waterfall 瀑布流

此组件瀑布流形式的展示列表数据,适合展示多条数据,常用于一些电商商品展示等,如某东、某宝首页。
平台差异说明
| APP | H5 | 微信小程序 | 支付宝小程序 | QQ小程序 | ··· |
|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | 适配中 |
代码示例
部分功能示例,具体可参考示例程序以及文档API。
基础用法(仅vue生效)
通过 columnCount 属性设置瀑布流列数,默认 2,建议范围1-5
- 列数为2,则需要定义两个插槽值,如:
<template v-slot:list1>...</template>、<template v-slot:list2">...</template> - 列数为3,则需要定义三个插槽值,如:
<template v-slot:list1>...</template>、<template v-slot:list2">...</template>、<template v-slot:list3">...</template>,更多列数以此类推
html
<template>
<view class="waterfall">
<fu-waterfall
ref="waterfallRef"
v-model="list"
columnCount="2"
:addTime="10"
:column-gap="columnGap"
@changeList="changeList">
<!-- 第一列数据 -->
<template v-slot:list1>
<!-- 为了兼容部分平台的BUG,必须套一层view -->
<view>
<view class="waterfall-item" v-for="(item, index) in waterfallData.list1" :key="item.id">
<view class="waterfall-item__image" :style="[imgStyle(item)]">
<image :src="item.image" mode="widthFix" :style="{width: `${item.width}px`}"></image>
</view>
<view class="waterfall-item__wrap">
<view class="waterfall-item__wrap--title">
<text>{{ item.title }}</text>
</view>
<view class="waterfall-item__wrap--desc fu-line-2">
<text>{{ item.desc }}</text>
</view>
</view>
</view>
</view>
</template>
<!-- 第二列数据 -->
<template v-slot:list2>
<!-- 为了兼容部分平台的BUG,必须套一层view -->
<view>
<view class="waterfall-item" v-for="(item, index) in waterfallData.list2" :key="item.id">
<view class="waterfall-item__image" :style="[imgStyle(item)]">
<image :src="item.image" mode="widthFix" :style="{width: `${item.width}px`}"></image>
</view>
<view class="waterfall-item__wrap">
<view class="waterfall-item__wrap--title">
<text>{{ item.title }}</text>
</view>
<view class="waterfall-item__wrap--desc fu-line-2">
<text>{{ item.desc }}</text>
</view>
</view>
</view>
</view>
</template>
</fu-waterfall>
</view>
</template>
<script setup>
import { ref, reactive, computed } from 'vue';
import { onLoad, onHide, onReachBottom } from '@dcloudio/uni-app';
// data数据
const waterfallRef = ref();
let list = reactive([]); // 瀑布流全部数据
let waterfallData = reactive({
list1: [], // 瀑布流第一列数据
list2: [] // 瀑布流第二列数据
})
const columnGap = ref(10);
// 生命周期
onLoad(() => {
init()
});
onHide(() => {
waterfallRef.value.clear()
});
onReachBottom(async () => {
const { data } = await initList();
Object.assign(list, list.concat(data))
});
// computed计算属性
const imgStyle = computed(() => {
return (item) => {
const baseW = uni.upx2px(750);
const adjusteW = (baseW - (columnGap.value * 3)) / 2;
const scale = adjusteW / item.w;
return {
width: `${adjusteW}px`,
height: `${scale * item.h}px`
}
}
})
// methods方法
// 这点非常重要:e.name在这里返回是list1或list2,要手动将数据追加到相应列
const changeList = (e) => {
waterfallData[e.name].push(e.value);
};
const init = async () => {
list.length = 0;
waterfallData.list1 = [];
waterfallData.list2 = [];
const { data } = await initList();
Object.assign(list, data)
};
// 模拟的后端数据
const initList = () => {
return new Promise((resolve)=>{
const imgs = [
{ url: 'https://picsum.photos/110/120', width: 110, height: 120 },
{ url: 'https://picsum.photos/210/220', width: 210, height: 220 },
{ url: 'https://picsum.photos/320/340', width: 320, height: 340 },
{ url: 'https://picsum.photos/430/460', width: 430, height: 460 },
{ url: 'https://picsum.photos/540/580', width: 540, height: 580 },
{ url: 'https://picsum.photos/650/686', width: 650, height: 656 },
{ url: 'https://picsum.photos/310/422', width: 310, height: 422 },
{ url: 'https://picsum.photos/320/430', width: 320, height: 430 },
{ url: 'https://picsum.photos/330/424', width: 330, height: 424 },
{ url: 'https://picsum.photos/300/532', width: 300, height: 532 },
{ url: 'https://picsum.photos/350/400', width: 350, height: 400 },
{ url: 'https://picsum.photos/380/470', width: 380, height: 470 }
];
let list = [];
const demoFn = (i)=>{
const randomIndex = Math.floor(Math.random() * 10);
return {
id: uni.$fu.uuid(),
image: `${imgs[randomIndex].url}?${uni.$fu.uuid()}`,
w: imgs[randomIndex].width,
h: imgs[randomIndex].height,
title: i % 2 == 0? `体验fusions-ui框架`: `fusions-ui支持多平台`,
desc: i % 2 == 0? `欢迎使用fusions-ui,uni-app生态专用的UI框架`: `开发者编写一套代码,可发布到Android、iOS、H5、及各种小程序`
}
};
// 模拟异步
setTimeout(() => {
for (let i = 0; i < 20; i++) {
list.push(demoFn(i));
}
resolve({data:list});
}, 200)
})
}
</script>
<style lang="scss">
page {
background: #f8f8f8;
}
</style>
<style lang="scss" scoped>
.waterfall-item {
background-color: #FFFFFF;
margin-bottom: 20rpx;
overflow: hidden;
border-radius: 12rpx;
overflow: hidden;
&__wrap {
padding: 20rpx;
&--title {
font-size: 30rpx;
font-weight: bold;
line-height: 48rpx;
margin-bottom: 10rpx;
}
&--desc {
font-size: 26rpx;
color: #666666;
}
}
}
</style>删除某项数据(仅vue生效)
- 此用法是在基础用法的代码基础上进行扩展
- 通过
ref获取到组件实例调用组件方法remove(id),注意需要带上数据中的id参数,执行之后,必须在@remove回调中处理列表数据
html
<template>
<view class="waterfall">
<fu-waterfall ref="waterfallRef" @remove="handleRemove">...</fu-waterfall>
</view>
</template>
<script setup>
import { ref } from 'vue';
// data数据
const waterfallRef = ref();
// methods方法
// 长按某项执行删除操作
const handleLong(item) => {
uni.showModal({
title: '提示',
content: '确定删除该条数据吗?',
success: (res) => {
if(res.confirm) {
waterfallRef.value.remove(item.id);
}
}
})
};
// 删除某项后返回对应ID,根据ID标识在列数据中手动删除该项数据
const handleRemove = (id) => {
const index1 = waterfallData.list1.findIndex(item => item.id == id);
if(index1 != -1) waterfallData.list1.splice(index1, 1);
const index2 = waterfallData.list2.findIndex(item => item.id == id);
if(index2 != -1) waterfallData.list2.splice(index2, 1);
};
</script>清空所有数据(仅vue生效)
- 此用法是在基础用法的代码基础上进行扩展
- 通过
ref获取到组件实例调用组件方法clear(),此方法执行后触发@clear回调 - 使用场景:下拉刷新数据或tab切换时更改数据,需要先清空数据,再去赋值新的数据等场景,以下拉刷新数据为例:
html
<template>
<view class="waterfall">
<fu-waterfall ref="waterfallRef" @clear="handleClear">...</fu-waterfall>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onPullDownRefresh } from '@dcloudio/uni-app';
// data数据
const waterfallRef = ref();
// 生命周期
onPullDownRefresh(async () => {
list.value = [];
waterfallRef.value.clear();
waterfallData.list1 = [];
waterfallData.list2 = [];
const { data } = await initList();
Object.assign(list, data)
uni.showToast({
title: '刷新成功',
icon: 'success'
})
uni.stopPullDownRefresh();
});
// methods方法
const handleClear = () => {
console.log('执行了clear');
};
</script>加载更多数据(仅vue生效)
- 此用法是在基础用法的代码基础上进行扩展
- 使用场景:一般使用在触底加载更多数据等场景,配合加载更多组件fu-loading-more
html
<template>
<view class="waterfall">
<fu-waterfall ref="waterfallRef">...</fu-waterfall>
<!-- 加载更多组件 -->
<fu-loading-more :loadingType="loadingType"></fu-loading-more>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onReachBottom } from '@dcloudio/uni-app';
// data数据
let loadingType = ref(0);
// 生命周期
onReachBottom(async () => {
if(loadingType.value == 0) {
loadingType.value = 1;
const { data } = await initList();
Object.assign(list, list.concat(data))
loadingType.value = 0;
}
});
</script>Tab切换案例(仅vue生效)
瀑布流是属于动态根据每项的高度计算该显示到哪列,当tab切换更换数据时,就需要将原有数据清除,重新赋值新数据,这里只是提供一个思路
html
<template>
<view class="waterfall">
<fu-tabs :list="tabsMenu" @click="handleTabs"></fu-tabs>
<fu-waterfall ...>
...
</fu-waterfall>
</view>
</template>
<script setup>
import { reactive } from 'vue';
// data数据
const tabsMenu = reactive([
{ name: '选项一' },
{ name: '选项二' }
]);
let list = reactive([]); // 瀑布流全部数据
let waterfallData = reactive({
list1: [], // 瀑布流第一列数据
list2: [] // 瀑布流第二列数据
});
// methods方法
const handleTabs = (e) => {
list.length = 0;
waterfallRef.value.clear();
waterfallData.list1 = [];
waterfallData.list2 = [];
const { data } = await initList();
Object.assign(list, data)
};
</script>优化方案(仅vue生效)
上面的示例中使用 image 标签展示图片,如果需要功能更强大图片组件(加载中,加载失败等反馈),可使用fu-image
- 通过
addTime属性可以设置每项显示的速度,值越小体验越好,但是可能会导出两列高度相差太大,这里可以提供一个优化思路:通过后端返回图片的宽高,然后再通过每列的宽度,最后计算出每张图片应该展示的宽高,然后给image嵌套一层<view style="width: xxxx; height: xxx;"></view>,提前将位置占好,这样计算出的每列高度就可以保证相近没有误差 - 如果页面还没有渲染结束,就离开页面了,但此时
@changeList回调还没有返回数据,可能会造成渲染出错,所以要想办法停止渲染,处理方式:
html
<script setup>
import { onHide } from '@dcloudio/uni-app';
// 生命周期
onHide(() => {
waterfallRef.value.clear()
});
</script>API
Waterfall Props
| 属性名 | 说明 | 类型 | 默认值 | 可选值 |
|---|---|---|---|---|
| v-model | 双向绑定瀑布流数据(仅vue生效) | Array | [] | - |
| idKey | 数据的id值,根据id值对数据执行删除操作,示例数据:{id: 1, name: 'fusions-ui'},那么此值设为为id(仅vue生效) | String | 'id' | - |
| idPrefix | 前置标识 | String | waterfall-flow- | - |
| addTime | 每次插入数据的事件间隔,间隔越长能保证两列高度相近,但是用户体验不好,单位ms(仅vue生效) | Number | 200 | - |
| columnCount | 瀑布流列数 | String|Number | 2 | 1~5 |
| columnGap | 列与列的间隙 | String|Number | 20 | - |
| showScrollbar | 是否显示滚动条(仅nvue生效) | Boolean | false | true |
| columnWidth | 列宽,单位px(仅nvue生效) | String|Number | auto | - |
| width | 瀑布流的宽度(仅nvue生效) | String|Number | 屏幕宽度 | - |
| height | 瀑布流的高度(仅nvue生效) | String|Number | 屏幕高度 | - |
Waterfall Methods
| 方法名 | 说明 | 参数 |
|---|---|---|
| clear | 清除瀑布流数据(仅vue生效) | - |
| remove | 清除指定的某一条数据,根据id来实现,删除后v-model绑定的数据会自动变化(仅vue生效) | () => void |
Waterfall Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| changeList | 【必须使用】处理数据时触发,为了兼容某些端不支持插槽回传参数的情况(仅vue生效) | 列表数据,columnCount=2时、第一次返回{list1:{...}},第二次返回{list2:{...}}...;返回后需要手动追加对应的列数据 |
| finish | 瀑布流加载完成触发事件(仅vue生效) | () => void |
| clear | 清空数据列表触发事件(仅vue生效) | () => void |
| remove | 删除列表中某条数据触发事件(仅vue生效) | (id: number) => void |
| scrolltolower | 滚动到底部触发事件(仅nvue生效) | () => void |
Waterfall Slot
| 名称 | 说明 |
|---|---|
| default | 瀑布流内容 |