<template>
<view>
<view class="waterfall-box h-flex-x h-flex-2" v-if="showList">
<view>
<view v-for="(item,index) in leftList" :key="item._render_id" class="list-item"
:class="{'show': showPage > item._current_page }">
<helang-waterfall-item :item="item" tag="left" :index="index" @height="onHeight"
@click="onClick"></helang-waterfall-item>
</view>
</view>
<view>
<view v-for="(item,index) in rightList" :key="item._render_id" class="list-item"
:class="{'show': showPage > item._current_page }">
<helang-waterfall-item :item="item" @height="onHeight" @click="onClick" tag="right"
:index="index"></helang-waterfall-item>
</view>
</view>
</view>
<slot name="default"></slot>
</view>
</template>
<script>
import helangWaterfallItem from "@/components/Waterfall/waterfall-item.vue"
export default {
name: "helangWaterfallList",
options: {
virtualHost: true
},
components: {
"helang-waterfall-item": helangWaterfallItem
},
props: {
// 组件状态
status: {
type: String,
default: ''
},
// 待渲染的数据
list: {
type: Array,
default () {
return [];
}
},
// 重置列表,设置为 true 时,瀑布流会自动重新渲染列表
reset: {
type: Boolean,
default: false
},
},
watch: {
"$props.status"(newValue, oldValue) {
// 状态变更为 加载成功 时,执行瀑布流数据渲染
if (newValue == 'success') {
this.startRender();
} else if (!this.showList) {
this.resetData();
}
}
},
computed: {
showList() {
return !["fail", "empty"].includes(this.$props.status);
}
},
data() {
return {
// 左侧列表高度
leftHeight: 0,
// 右侧列表高度
rightHeight: 0,
// 左侧列表数据
leftList: [],
// 右侧列表数据
rightList: [],
// 待渲染列表
awaitRenderList: [],
// 当前展示页码数据
showPage: 1
}
},
mounted() {
if (this.$props.status == 'success') {
this.startRender();
}
},
methods: {
// 监听高度变化
onHeight(height, tag) {
/**
* 这个为实际渲染后 CSS 中 margin-buttom 的值,本示例默认为20rpx
* 用于解决实际渲染后因为数据条数关系,高度差计算偏差的问题
* */
let marginBottom = uni.upx2px(20);
// console.log(`左高:${this.leftHeight},右高:${this.rightHeight},当前高:${height},插入方向:${tag}`)
if (tag == 'left') {
this.leftHeight += (height + marginBottom);
} else {
this.rightHeight += (height + marginBottom);
}
this.renderList();
},
// 组件点击事件
onClick(index, tag) {
// 对应的数据
if (tag == 'left') {
this.$emit("click", this.leftList[index], index, tag);
} else {
this.$emit("click", this.rightList[index], index, tag);
}
},
// 渲染列表,这里实现瀑布流的左右分栏
renderList() {
// 待渲染长度为 0 时表示已渲染完成
if (this.awaitRenderList.length < 1) {
this.showPage++;
this.$emit("done");
// 为防止 js 数值类型最大值溢出,当高度值大于 1亿时重置高度
if (this.leftHeight > 100000000) {
if (this.leftHeight > this.rightHeight) {
this.leftHeight = 2;
this.rightHeight = 1;
} else {
this.leftHeight = 1;
this.rightHeight = 2;
}
}
return;
}
let item = {
...this.awaitRenderList.splice(0, 1)[0],
// 当前数据添加当前页面标识
_current_page: this.showPage,
// 当前数据添加一个渲染id,解决 v-for 重复会出现不执行 load 的 BUG
_render_id: new Date().getTime()
};
if (this.leftHeight > this.rightHeight) {
this.rightList.push(item);
} else {
this.leftList.push(item);
}
},
// 重置数据
resetData() {
this.leftHeight = 0;
this.rightHeight = 0;
this.leftList = [];
this.rightList = [];
this.awaitRenderList = [];
// 当前展示页码数据
this.showPage = 1;
},
// 启动渲染
startRender() {
if (!this.showList) {
this.resetData();
return;
}
if (!this.$props.list || this.$props.list.length < 1) {
return;
}
// 若本次渲染为 重置 则先恢复组件的默认参数
if (this.$props.reset) {
this.resetData();
}
this.awaitRenderList = [...this.$props.list];
this.renderList();
}
}
}
</script>
<style lang="scss" scoped>
.waterfall-box {
padding: 0rpx 13rpx 0rpx 13rpx;
box-sizing: border-box;
>view {
padding: 0rpx 10rpx;
}
.list-item {
margin-bottom: 0;
// 设置透明,默认是可视的
opacity: 0;
// 默认超出隐藏,不影响加载中的文字显示效果
overflow: hidden;
height: 0;
&.show {
margin-bottom: 5rpx;
opacity: 1;
overflow: auto;
height: auto;
}
}
}
.h-flex-x {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
&.h-flex-2 {
>view {
width: 50%;
}
}
}
</style>