Skip to content

Commit

Permalink
feat: Waterfall
Browse files Browse the repository at this point in the history
  • Loading branch information
kailong321200875 committed Oct 7, 2023
1 parent 69ec5f0 commit d543e56
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 42 deletions.
145 changes: 106 additions & 39 deletions src/components/Waterfall/src/Waterfall.vue
@@ -1,89 +1,156 @@
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign'
import { ref, nextTick, unref, onMounted } from 'vue'
import { isString } from '@/utils/is'
import { ref, nextTick, unref, onMounted, watch } from 'vue'
import { useEventListener, useIntersectionObserver } from '@vueuse/core'
import { debounce } from 'lodash-es'
const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('waterfall')
const emit = defineEmits(['loadMore'])
const prop = defineProps({
data: propTypes.arrayOf(propTypes.any),
reset: propTypes.bool.def(false),
reset: propTypes.bool.def(true),
width: propTypes.number.def(200),
height: propTypes.number.def(0),
gap: propTypes.number.def(20),
getContainer: propTypes.func.def(() => document.body),
props: propTypes.objectOf(propTypes.string).def({
src: 'src',
height: 'height'
})
}),
loadingText: propTypes.string.def('加载中...'),
loading: propTypes.bool.def(false),
end: propTypes.bool.def(false),
endText: propTypes.string.def('没有更多了')
})
const wrapEl = ref<HTMLDivElement>()
const heights = ref<number[]>([])
const wrapHeight = ref(0)
const wrapWidth = ref(0)
const loadMore = ref<HTMLDivElement>()
// 首先确定列数 = 页面宽度 / 图片宽度
const cols = ref(0)
const filterData = ref<any[]>([])
const filterWaterfall = async () => {
const { props, width, gap, getContainer, height } = prop
const { props, width, gap } = prop
const data = prop.data as any[]
await nextTick()
const container = (getContainer?.() || unref(wrapEl)) as HTMLElement
const container = unref(wrapEl) as HTMLElement
if (!container) return
cols.value = Math.floor(container.clientWidth / (width + gap))
const length = data.length
for (let i = 0; i < length; i++) {
if (i + 1 < unref(cols)) {
if (height || data[i][props.height as string]) {
// 如果有全局高度,就使用全局高度
// 如果 data[i][props.height as string] 是字符串,只保留数字字符串
const itemHeight = isString(data[i][props.height as string])
? Number(data[i][props.height as string].replace(/[^0-9]/gi, ''))
: data[i][props.height as string]
heights.value[i] = height || itemHeight
} else {
// 说明在第一列
const itemEl = container.querySelector(`.${prefixCls}-item__${i}`)
itemEl?.addEventListener('load', () => {
const clientRect = itemEl?.getBoundingClientRect()
console.log(clientRect)
})
// const imgEl = new Image()
// imgEl.src = data[i][props.src as string]
// imgEl.onload = async () => {
// // const itemEl = container.querySelector(`.${prefixCls}-item__${i}`)
// const clientRect = itemEl?.getBoundingClientRect()
// if (clientRect) {
// heights.value[i] = clientRect?.height
// }
// }
if (i < unref(cols)) {
heights.value[i] = data[i][props.height as string]
filterData.value.push({
...data[i],
top: 0,
left: i * (width + gap)
})
} else {
// 其他行,先找出最矮的那一列 和 索引
// 假设最小高度是第一个元素
let minHeight = heights.value[0]
let index = 0
// 找出最小高度
for (let j = 1; j < cols.value; j++) {
if (unref(heights)[j] < minHeight) {
minHeight = unref(heights)[j]
index = j
}
}
// 更新最矮高度
heights.value[index] += data[i][props.height as string] + gap
filterData.value.push({
...data[i],
top: minHeight + gap,
left: index * (width + gap)
})
}
}
wrapHeight.value = Math.max(...unref(heights))
wrapWidth.value = unref(cols) * (width + gap) - gap
}
watch(
() => prop.data,
async () => {
await nextTick()
filterWaterfall()
},
{
immediate: true
}
)
onMounted(() => {
filterWaterfall()
if (unref(prop.reset)) {
useEventListener(window, 'resize', debounce(filterWaterfall, 300))
}
useIntersectionObserver(
unref(loadMore),
([{ isIntersecting }]) => {
if (isIntersecting && !prop.loading && !prop.end) {
emit('loadMore')
}
},
{
threshold: 0.1
}
)
})
</script>

<template>
<div :class="prefixCls" ref="wrapEl">
<div
:class="[prefixCls, 'flex', 'justify-center', 'items-center']"
ref="wrapEl"
:style="{
height: `${wrapHeight + 40}px`
}"
>
<div
v-for="(item, $index) in data"
:class="`${prefixCls}-item__${$index}`"
:key="`water-${$index}`"
class="relative"
:style="{
width: `${width}px`
width: `${wrapWidth}px`,
height: `${wrapHeight + 40}px`
}"
>
<img :src="item[props.src as string]" class="w-full block" alt="" srcset="" />
<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="" />
</div>
<div
ref="loadMore"
class="h-40px flex justify-center absolute w-full"
:style="{
top: `${wrapHeight + gap}px`
}"
>
{{ end ? endText : loadingText }}
</div>
</div>
</div>
</template>
2 changes: 1 addition & 1 deletion src/config/axios/service.ts
Expand Up @@ -32,7 +32,7 @@ axiosInstance.interceptors.response.use(
return res
},
(error: AxiosError) => {
console.log('err' + error) // for debug
console.log('err' + error) // for debug
ElMessage.error(error.message)
return Promise.reject(error)
}
Expand Down
26 changes: 24 additions & 2 deletions src/views/Components/Waterfall.vue
Expand Up @@ -11,29 +11,51 @@ const data = ref<any>([])
const getList = () => {
const list: any = []
for (let i = 0; i < 20; i++) {
// 随机 100, 500 之间的整数
const height = Mock.Random.integer(100, 500)
const width = Mock.Random.integer(100, 500)
list.push(
Mock.mock({
width,
height,
id: toAnyString(),
image_uri: Mock.Random.image('@integer(100, 500)x@integer(100, 500)')
image_uri: Mock.Random.image(`${width}x${height}`)
})
)
}
data.value = [...unref(data), ...list]
console.log('【data】:', data.value)
if (unref(data).length >= 60) {
end.value = true
}
}
getList()
const { t } = useI18n()
const loading = ref(false)
const end = ref(false)
const loadMore = () => {
loading.value = true
setTimeout(() => {
getList()
loading.value = false
}, 1000)
}
</script>

<template>
<ContentWrap :title="t('router.waterfall')">
<Waterfall
:data="data"
:loading="loading"
:end="end"
:props="{
src: 'image_uri',
height: 'height'
}"
@load-more="loadMore"
/>
</ContentWrap>
</template>

0 comments on commit d543e56

Please sign in to comment.