Skip to content

Waterfall 瀑布流组件

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

温馨提示

本项目参考了 uView-Plus 开源项目的组件开发方式,基于 Vue 3 和 TypeScript 实现了自定义组件。目前该组件仍处于测试阶段。
感谢 uView-Plus 开源项目及其团队成员的贡献,他们的组件开发思路为本项目提供了宝贵地参考。如果需要了解更多组件开发细节,可以参考uView-Plus的 waterfall组件 的代码实现。

提示

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

平台差异说明

APP(vue)H5微信小程序

基本使用示例

html
<!-- 全局使用 -->
<hy-waterfall v-model="flowList"></hy-waterfall>
<!-- 单个组件引入 -->
<HyWaterfall v-model="flowList"></HyWaterfall>
ts
import { HyWaterfall } from "hy-app"

完整应用示例

vue
<template>
  <view class="waterfall">
    <hy-waterfall v-model="flowList" ref="waterfallRef">
      <template v-slot:left="{ leftList }">
        <view
            class="demo-water"
            v-for="(item, index) in leftList"
            :key="index"
        >
          <!-- 微信小程序需要hx2.8.11版本才支持在template中引入其他组件,比如下方的u-lazy-load组件 -->
          <hy-image
              width="100%"
              mode="widthFix"
              radius="5"
              :src="item.image"
          ></hy-image>
          <view class="demo-title">{{ item.title }}</view>
          <view class="demo-price">{{ item.price }}元</view>
          <view class="demo-tag">
            <view class="demo-tag-owner">
              <text class="text">自营</text>
            </view>
            <view class="demo-tag-text">
              <text class="text">放心购</text>
            </view>
          </view>
          <view class="demo-shop">{{ item.shop }}</view>
          <view class="u-close">
            <up-icon
                name="close-circle-fill"
                color="#fa3534"
                size="16"
                @click="remove(item.id)"
            ></up-icon>
          </view>
        </view>
      </template>
      <template v-slot:right="{ rightList }">
        <view
            class="demo-water"
            v-for="(item, index) in rightList"
            :key="index"
        >
          <hy-image
              width="100%"
              mode="widthFix"
              radius="5"
              :src="item.image"
          ></hy-image>
          <view class="demo-title">{{ item.title }}</view>
          <view class="demo-price">{{ item.price }}元</view>
          <view class="demo-tag">
            <view class="demo-tag-owner">
              <text class="text">自营</text>
            </view>
            <view class="demo-tag-text">
              <text class="text">放心购</text>
            </view>
          </view>
          <view class="demo-shop">{{ item.shop }}</view>
          <view class="u-close">
            <up-icon
                name="close-circle-fill"
                color="#fa3534"
                size="34"
                @click="remove(item.id)"
            ></up-icon>
          </view>
        </view>
      </template>
    </hy-waterfall>
  </view>
</template>

<script setup lang="ts">
  import { onMounted, ref } from "vue";
  import { guid, random } from "hy-app";
  import list from "./index";
  import { onReachBottom } from "@dcloudio/uni-app";
  

  const flowList = ref<Record<string, any>[]>([]);
  const waterfallRef = ref();
  const loadStatus = ref("loadmore");

  onMounted(() => {
    addRandomData();
  });
  onReachBottom(() => {
    loadStatus.value = "loading";
    // 模拟数据加载
    setTimeout(() => {
      addRandomData();
      loadStatus.value = "loadmore";
    }, 1000);
  });

  const addRandomData = () => {
    for (let i = 0; i < 20; i++) {
      let index = random(0, list.length - 1);
      // 先转成字符串再转成对象,避免数组对象引用导致数据混乱
      let item = JSON.parse(JSON.stringify(list[index]));
      item.id = guid();
      flowList.value.push(item);
    }
  };
  const remove = (id: string) => {
    waterfallRef.value.remove(id);
  };
  const clear = () => {
    waterfallRef.value.clear();
  };
</script>

<style scoped lang="scss">
  @import "./index.scss";
</style>
javascript
export default [
  {
    price: "1300万",
    title: "法拉利LaFerrari",
    shop: "性能超群,领跑超级跑车界",
    image:
      "https://pic.rmb.bdstatic.com/bjh/news/68eb7f6d762684a7f81d91edd1190cde.jpeg",
  },
  {
    price: "3300万",
    title: "布加迪 Chiron",
    shop: "性能超群,领跑超级跑车界",
    image: "https://img1.baidu.com/it/u=710933577,1433411533&fm=253",
  },
  {
    price: "2000万",
    title: "法拉利296 GTS",
    shop: "新时代混动法拉利敞篷跑车",
    image:
      "https://pic.rmb.bdstatic.com/bjh/240514/news/3ed50c020490bdf65832b56bfdec9edf6399.jpeg",
  },
  {
    price: "784万",
    title: "柯尼塞格Jesko",
    shop: "一头拥有超强马力和极致速度的“猛兽”",
    image: "https://img2.baidu.com/it/u=2465835289,2125464963&fm=253",
  },
  {
    price: "9000万",
    title: "兰博基尼Temerario",
    shop: "李白杜甫白居易旗舰店",
    image:
      "https://pic.rmb.bdstatic.com/da708645a2bce38179625e8364c05a76.jpg@h_1280",
  },
  {
    price: "400万",
    shop: "保时捷963",
    title: "保时捷963",
    image:
      "https://img0.baidu.com/it/u=3402753783,3081776286&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
  },
  {
    price: "233万",
    shop: "玛莎拉蒂Gran Turismo",
    title: "玛莎拉蒂Gran Turismo",
    image:
      "https://img1.baidu.com/it/u=1729207840,2645487814&fm=253&fmt=auto?w=800&h=1002",
  },
  {
    price: "1654万",
    title: "阿斯顿马丁",
    shop: "李白杜甫白居易旗舰店",
    image: "https://img1.baidu.com/it/u=3621246908,3142164736&fm=253",
  },
  {
    price: "1678万",
    title: "迈凯伦",
    shop: "李白杜甫白居易旗舰店",
    image: "https://img0.baidu.com/it/u=2821221440,1024182410&fm=253",
  },
  {
    price: "924万",
    title: "兰博基尼 电鳗",
    shop: "李白杜甫白居易旗舰店",
    image:
      "https://inews.gtimg.com/om_bt/Opd--mKoy4ZOIDzMAOz2bsY6i1cxTSHtREhKoNX2CaBY4AA/641",
  },
  {
    price: "8243万",
    title: "克尔维特",
    shop: "克尔维特",
    image: "https://b0.bdstatic.com/b72b6ed36682acf2aad6f0fa853111c5.jpeg",
  },
];
scss
.waterfall {
  background-color: #f0e6ef;
}
.demo-water {
  border-radius: 8px;
  margin: 5px;
  background-color: #ffffff;
  padding: 8px;
  position: relative;
  /* #ifdef H5 */
  cursor: pointer;
  /* #endif */
  .u-close {
    position: absolute;
    top: -7px;
    right: 3px;
    opacity: 0;
  }
  /* #ifdef H5 */
  &:hover {
    .u-close {
      opacity: 1;
    }
  }
  /* #endif */
}

.demo-img-wrap {
}

.demo-image {
  width: 100%;
  border-radius: 4px;
}

.demo-title {
  font-size: 30rpx;
  margin-top: 5px;
  color: $u-main-color;
  /* #ifndef APP-NVUE */
  word-break: break-all;
  /* #endif */
}

.demo-tag {
  display: flex;
  flex-direction: row;
  margin-top: 5px;
}

.demo-tag-owner {
  background-color: $u-error;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px 7px;
  border-radius: 20px;
  line-height: 1;
  .text {
    font-size: 12px;
    color: #ffffff;
  }
}

.demo-tag-text {
  border: 1px solid $u-primary;
  margin-left: 10px;
  border-radius: 50rpx;
  line-height: 1;
  padding: 2px 7px;
  border-radius: 20px;
  display: flex;
  align-items: center;
  border-radius: 20px;
  .text {
    font-size: 12px;
    color: $u-primary;
  }
}

.demo-price {
  font-size: 30rpx;
  color: $u-error;
  margin-top: 5px;
}

.demo-shop {
  font-size: 22rpx;
  color: $u-tips-color;
  margin-top: 5px;
}

注意事项

组件有一个add-time参数,用于将单条数据添加到队列的时间间隔,因为图片加载是需要时间的,所以瀑布流左右列 的高度会不定时改变,add-time值越大,对程序效果越好,但是对用户来说,越大值可能就是以能感受的速度一个一个添加 到队列尾部的,所以这是一个双面性的结果。

API

参数说明类型默认值
v-modal接收列表值array-
addTime单条数据添加到队列的时间间隔,单位msnumber200
idKey数据的唯一值的键名stringid

Methods

事件名说明传参
clear清空列表数据-
remove删除某条数据id:唯一值,需要和idKey一致

Slots

插槽名说明接收值
left左边内容插槽-
right右边内容插槽-
00:46