226 lines
4.4 KiB
Vue
226 lines
4.4 KiB
Vue
<template>
|
||
<view class="cl-waterfall" v-if="visible">
|
||
<!-- 纵向 -->
|
||
<template v-if="direction === 'vertical'">
|
||
<view class="cl-waterfall__item" v-for="(item, index) in list" :key="index">
|
||
<slot :item="item" :index="index"></slot>
|
||
</view>
|
||
</template>
|
||
|
||
<!-- 横向 -->
|
||
<template v-else-if="direction === 'horizontal'">
|
||
<slot :list="list"></slot>
|
||
</template>
|
||
|
||
<!-- 空态 -->
|
||
<slot name="empty" v-if="showEmpty">
|
||
<cl-empty />
|
||
</slot>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="ts">
|
||
import { type PropType, computed, defineComponent, ref, nextTick, getCurrentInstance } from "vue";
|
||
import { flatMap, isEmpty } from "lodash-es";
|
||
import { parseRpx } from "/@/cool/utils";
|
||
import { onShow } from "@dcloudio/uni-app";
|
||
|
||
export default defineComponent({
|
||
name: "cl-waterfall",
|
||
|
||
props: {
|
||
// 列的数量
|
||
column: {
|
||
type: Number,
|
||
default: 2,
|
||
},
|
||
// 列间隔
|
||
gutter: {
|
||
type: Number,
|
||
default: 20,
|
||
},
|
||
// 布局方向
|
||
direction: {
|
||
type: String as PropType<"horizontal" | "vertical">,
|
||
default: "horizontal",
|
||
},
|
||
// 匹配值
|
||
nodeKey: {
|
||
type: String,
|
||
default: "id",
|
||
},
|
||
},
|
||
|
||
setup(props) {
|
||
const { proxy }: any = getCurrentInstance();
|
||
|
||
const visible = ref(true);
|
||
|
||
// 列表
|
||
const list = ref<any[]>([]);
|
||
|
||
// 每次追加的数据
|
||
let data: any[] = [];
|
||
|
||
// 列数量
|
||
const columnCount = computed(() => (props.direction == "vertical" ? props.column : "none"));
|
||
|
||
// 列间距
|
||
const columnGap = computed(() =>
|
||
props.direction == "vertical" ? parseRpx(props.gutter) : "none",
|
||
);
|
||
|
||
// 是否空
|
||
const showEmpty = computed(() => {
|
||
return list.value.filter((e) => !isEmpty(e)).length == 0;
|
||
});
|
||
|
||
// 刷新列表
|
||
function refresh(data: any[]) {
|
||
visible.value = false;
|
||
|
||
nextTick(() => {
|
||
visible.value = true;
|
||
|
||
switch (props.direction) {
|
||
case "horizontal":
|
||
list.value = new Array(props.column).fill(1).map(() => []);
|
||
|
||
// 等待 cl-waterfall-column 渲染完成后追加数据
|
||
nextTick(() => {
|
||
append(data);
|
||
});
|
||
break;
|
||
case "vertical":
|
||
list.value = data;
|
||
break;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 计算高度,一个个往列表追加
|
||
async function append(arr: any[]) {
|
||
data = arr;
|
||
|
||
for (let i = 0; i < arr.length; i++) {
|
||
const next = async () => {
|
||
const rects = await getRect();
|
||
|
||
// 获取不到的时候阻断掉
|
||
if (isEmpty(rects) && i !== 0) {
|
||
return false;
|
||
}
|
||
|
||
// 获取 cl-waterfall-column 的高度,比较后在最小的列中塞入
|
||
return Promise.all(rects).then((res) => {
|
||
let colsHeight = res.map((e) => e.height);
|
||
|
||
let minH = Math.min(...colsHeight);
|
||
let index = colsHeight.indexOf(minH);
|
||
|
||
if (index < 0) {
|
||
index = 0;
|
||
}
|
||
|
||
list.value[index]?.push(arr[i]);
|
||
|
||
return true;
|
||
});
|
||
};
|
||
|
||
await next();
|
||
}
|
||
}
|
||
|
||
// 更新单条数据,根据nodeKey来匹配
|
||
function update(id: string | number, data: any) {
|
||
const next = (e: any) => {
|
||
const d = e[props.nodeKey] === id;
|
||
|
||
if (d) {
|
||
Object.assign(e, data);
|
||
}
|
||
|
||
return Boolean(d);
|
||
};
|
||
|
||
switch (props.direction) {
|
||
case "horizontal":
|
||
list.value.find((col) => {
|
||
return col.find(next);
|
||
});
|
||
break;
|
||
case "vertical":
|
||
list.value.find(next);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 清空列表
|
||
function clear() {
|
||
list.value = [];
|
||
}
|
||
|
||
// 获取列
|
||
function getRect(): Promise<any> {
|
||
return new Promise((resolve) => {
|
||
// #ifdef MP
|
||
let timer: any = null;
|
||
|
||
function fn() {
|
||
const children: any[] = proxy.$children;
|
||
|
||
if (isEmpty(children)) {
|
||
timer = setTimeout(() => {
|
||
fn();
|
||
}, 50);
|
||
} else {
|
||
clearTimeout(timer);
|
||
const arr = children.filter((e) => e.getRect).map((e) => e.getRect());
|
||
resolve(arr);
|
||
}
|
||
}
|
||
|
||
fn();
|
||
// #endif
|
||
|
||
// #ifndef MP
|
||
nextTick(() => {
|
||
uni.createSelectorQuery()
|
||
.in(proxy.$root)
|
||
.selectAll(`.cl-waterfall-column`)
|
||
.boundingClientRect(resolve)
|
||
.exec();
|
||
});
|
||
// #endif
|
||
});
|
||
}
|
||
|
||
// 重新布局,在加载中切换页面导致计算错误,返回页面时 onShow 重新计算
|
||
function doLayout() {
|
||
// 列空的时候 重新追加数据
|
||
if (isEmpty(flatMap(list.value)) && !isEmpty(data)) {
|
||
append(data);
|
||
}
|
||
}
|
||
|
||
onShow(() => {
|
||
doLayout();
|
||
});
|
||
|
||
return {
|
||
visible,
|
||
list,
|
||
columnCount,
|
||
columnGap,
|
||
refresh,
|
||
append,
|
||
update,
|
||
clear,
|
||
showEmpty,
|
||
doLayout,
|
||
};
|
||
},
|
||
});
|
||
</script>
|