Skip to content

Waterfall 瀑布流

iPhone

此组件瀑布流形式的展示列表数据,适合展示多条数据,常用于一些电商商品展示等,如某东、某宝首页。

平台差异说明

APPH5微信小程序支付宝小程序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前置标识Stringwaterfall-flow--
addTime每次插入数据的事件间隔,间隔越长能保证两列高度相近,但是用户体验不好,单位ms(仅vue生效)Number200-
columnCount瀑布流列数String|Number21~5
columnGap列与列的间隙String|Number20-
showScrollbar是否显示滚动条(仅nvue生效)Booleanfalsetrue
columnWidth列宽,单位px(仅nvue生效)String|Numberauto-
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瀑布流内容