|
@@ -20,10 +20,13 @@ const prop = defineProps({
|
|
|
src: 'src',
|
|
|
height: 'height'
|
|
|
}),
|
|
|
+ cols: propTypes.number.def(undefined),
|
|
|
loadingText: propTypes.string.def('加载中...'),
|
|
|
loading: propTypes.bool.def(false),
|
|
|
end: propTypes.bool.def(false),
|
|
|
- endText: propTypes.string.def('没有更多了')
|
|
|
+ endText: propTypes.string.def('没有更多了'),
|
|
|
+ autoCenter: propTypes.bool.def(true),
|
|
|
+ layout: propTypes.oneOf(['javascript', 'flex']).def('flex')
|
|
|
})
|
|
|
|
|
|
const wrapEl = ref<HTMLDivElement>()
|
|
@@ -37,7 +40,7 @@ const wrapWidth = ref(0)
|
|
|
const loadMore = ref<HTMLDivElement>()
|
|
|
|
|
|
// 首先确定列数 = 页面宽度 / 图片宽度
|
|
|
-const cols = ref(0)
|
|
|
+const innerCols = ref(0)
|
|
|
|
|
|
const filterData = ref<any[]>([])
|
|
|
|
|
@@ -49,11 +52,11 @@ const filterWaterfall = async () => {
|
|
|
|
|
|
const container = unref(wrapEl) as HTMLElement
|
|
|
if (!container) return
|
|
|
- cols.value = Math.floor(container.clientWidth / (width + gap))
|
|
|
+ innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
|
|
|
|
|
|
const length = data.length
|
|
|
for (let i = 0; i < length; i++) {
|
|
|
- if (i < unref(cols)) {
|
|
|
+ if (i < unref(innerCols)) {
|
|
|
heights.value[i] = data[i][props.height as string]
|
|
|
filterData.value.push({
|
|
|
...data[i],
|
|
@@ -66,7 +69,7 @@ const filterWaterfall = async () => {
|
|
|
let minHeight = heights.value[0]
|
|
|
let index = 0
|
|
|
// 找出最小高度
|
|
|
- for (let j = 1; j < unref(cols); j++) {
|
|
|
+ for (let j = 1; j < unref(innerCols); j++) {
|
|
|
if (unref(heights)[j] < minHeight) {
|
|
|
minHeight = unref(heights)[j]
|
|
|
index = j
|
|
@@ -83,14 +86,42 @@ const filterWaterfall = async () => {
|
|
|
}
|
|
|
}
|
|
|
wrapHeight.value = Math.max(...unref(heights))
|
|
|
- wrapWidth.value = unref(cols) * (width + gap) - gap
|
|
|
+ wrapWidth.value = unref(innerCols) * (width + gap) - gap
|
|
|
}
|
|
|
|
|
|
-watch(
|
|
|
- () => prop.data,
|
|
|
- async () => {
|
|
|
- await nextTick()
|
|
|
+const flexWaterfall = async () => {
|
|
|
+ const { width, gap } = prop
|
|
|
+ const data = prop.data as any[]
|
|
|
+ await nextTick()
|
|
|
+
|
|
|
+ const container = unref(wrapEl) as HTMLElement
|
|
|
+ if (!container) return
|
|
|
+ innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
|
|
|
+
|
|
|
+ const length = data.length
|
|
|
+ // 根据列数,创建数组
|
|
|
+ const arr = new Array(unref(innerCols)).fill([])
|
|
|
+ // 循环data,依次插入到arr中
|
|
|
+ for (let i = 0; i < length; i++) {
|
|
|
+ const index = i % unref(innerCols)
|
|
|
+ arr[index] = [...arr[index], data[i]]
|
|
|
+ }
|
|
|
+ filterData.value = arr
|
|
|
+}
|
|
|
+
|
|
|
+const initLayout = () => {
|
|
|
+ const { layout } = prop
|
|
|
+ if (layout === 'javascript') {
|
|
|
filterWaterfall()
|
|
|
+ } else if (layout === 'flex') {
|
|
|
+ flexWaterfall()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [prop.data, prop.cols],
|
|
|
+ () => {
|
|
|
+ initLayout()
|
|
|
},
|
|
|
{
|
|
|
immediate: true
|
|
@@ -99,7 +130,7 @@ watch(
|
|
|
|
|
|
onMounted(() => {
|
|
|
if (unref(prop.reset)) {
|
|
|
- useEventListener(window, 'resize', debounce(filterWaterfall, 300))
|
|
|
+ useEventListener(window, 'resize', debounce(initLayout, 300))
|
|
|
}
|
|
|
useIntersectionObserver(
|
|
|
unref(loadMore),
|
|
@@ -117,41 +148,87 @@ onMounted(() => {
|
|
|
|
|
|
<template>
|
|
|
<div
|
|
|
- :class="[prefixCls, 'flex', 'justify-center', 'items-center']"
|
|
|
+ :class="[
|
|
|
+ prefixCls,
|
|
|
+ 'flex',
|
|
|
+ 'items-center',
|
|
|
+ {
|
|
|
+ 'justify-center': autoCenter
|
|
|
+ }
|
|
|
+ ]"
|
|
|
ref="wrapEl"
|
|
|
:style="{
|
|
|
- height: `${wrapHeight + 40}px`
|
|
|
+ height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
|
|
|
}"
|
|
|
>
|
|
|
- <div
|
|
|
- class="relative"
|
|
|
- :style="{
|
|
|
- width: `${wrapWidth}px`,
|
|
|
- height: `${wrapHeight + 40}px`
|
|
|
- }"
|
|
|
- >
|
|
|
- <div
|
|
|
- v-for="(item, $index) in filterData"
|
|
|
- :class="[`${prefixCls}-item__${$index}`, 'absolute']"
|
|
|
- :key="`water-${$index}`"
|
|
|
- :style="{
|
|
|
- width: `${width}px`,
|
|
|
- height: `${item[props.height as string]}px`,
|
|
|
- top: `${item.top}px`,
|
|
|
- left: `${item.left}px`
|
|
|
- }"
|
|
|
- >
|
|
|
- <img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
|
|
|
+ <template v-if="layout === 'javascript'">
|
|
|
+ <div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
|
|
|
+ <div
|
|
|
+ v-for="(item, $index) in filterData"
|
|
|
+ :class="[
|
|
|
+ `${prefixCls}-item__${$index}`,
|
|
|
+ {
|
|
|
+ absolute: layout === 'javascript'
|
|
|
+ }
|
|
|
+ ]"
|
|
|
+ :key="`water-${$index}`"
|
|
|
+ :style="{
|
|
|
+ width: `${width}px`,
|
|
|
+ height: `${item[props.height as string]}px`,
|
|
|
+ top: `${item.top}px`,
|
|
|
+ left: `${item.left}px`
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ ref="loadMore"
|
|
|
+ class="h-40px flex justify-center absolute w-full"
|
|
|
+ :style="{
|
|
|
+ top: `${wrapHeight + gap}px`
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ {{ end ? endText : loadingText }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </template>
|
|
|
+ <template v-else-if="layout === 'flex'">
|
|
|
<div
|
|
|
- ref="loadMore"
|
|
|
- class="h-40px flex justify-center absolute w-full"
|
|
|
+ class="relative flex pb-40px"
|
|
|
:style="{
|
|
|
- top: `${wrapHeight + gap}px`
|
|
|
+ width: cols ? '100%' : 'auto'
|
|
|
}"
|
|
|
>
|
|
|
- {{ end ? endText : loadingText }}
|
|
|
+ <div
|
|
|
+ v-for="(item, $index) in filterData"
|
|
|
+ :key="`waterWrap-${$index}`"
|
|
|
+ class="flex-1"
|
|
|
+ :style="{
|
|
|
+ marginRight: $index === filterData.length - 1 ? '0' : `${gap}px`
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="(child, i) in item"
|
|
|
+ :key="`waterWrap-${$index}-${i}`"
|
|
|
+ :style="{
|
|
|
+ marginBottom: `${gap}px`,
|
|
|
+ width: cols ? '100%' : `${width}px`,
|
|
|
+ height: cols ? 'auto' : `${child[props.height as string]}px`
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <img :src="child[props.src as string]" class="w-full h-full block" alt="" srcset="" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ ref="loadMore"
|
|
|
+ class="h-40px flex justify-center absolute w-full items-center"
|
|
|
+ :style="{
|
|
|
+ bottom: 0
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ {{ end ? endText : loadingText }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ </template>
|
|
|
</div>
|
|
|
</template>
|