feat: Waterfall

This commit is contained in:
kailong321200875 2023-10-07 16:43:03 +08:00
parent 69ec5f0d7d
commit d543e56efb
3 changed files with 131 additions and 42 deletions

View File

@ -1,89 +1,156 @@
<script lang="ts" setup> <script lang="ts" setup>
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { useDesign } from '@/hooks/web/useDesign' import { useDesign } from '@/hooks/web/useDesign'
import { ref, nextTick, unref, onMounted } from 'vue' import { ref, nextTick, unref, onMounted, watch } from 'vue'
import { isString } from '@/utils/is' import { useEventListener, useIntersectionObserver } from '@vueuse/core'
import { debounce } from 'lodash-es'
const { getPrefixCls } = useDesign() const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('waterfall') const prefixCls = getPrefixCls('waterfall')
const emit = defineEmits(['loadMore'])
const prop = defineProps({ const prop = defineProps({
data: propTypes.arrayOf(propTypes.any), data: propTypes.arrayOf(propTypes.any),
reset: propTypes.bool.def(false), reset: propTypes.bool.def(true),
width: propTypes.number.def(200), width: propTypes.number.def(200),
height: propTypes.number.def(0),
gap: propTypes.number.def(20), gap: propTypes.number.def(20),
getContainer: propTypes.func.def(() => document.body),
props: propTypes.objectOf(propTypes.string).def({ props: propTypes.objectOf(propTypes.string).def({
src: 'src', src: 'src',
height: 'height' 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 wrapEl = ref<HTMLDivElement>()
const heights = ref<number[]>([]) const heights = ref<number[]>([])
const wrapHeight = ref(0)
const wrapWidth = ref(0)
const loadMore = ref<HTMLDivElement>()
// = / // = /
const cols = ref(0) const cols = ref(0)
const filterData = ref<any[]>([])
const filterWaterfall = async () => { const filterWaterfall = async () => {
const { props, width, gap, getContainer, height } = prop const { props, width, gap } = prop
const data = prop.data as any[] const data = prop.data as any[]
await nextTick() await nextTick()
const container = (getContainer?.() || unref(wrapEl)) as HTMLElement const container = unref(wrapEl) as HTMLElement
if (!container) return if (!container) return
cols.value = Math.floor(container.clientWidth / (width + gap)) cols.value = Math.floor(container.clientWidth / (width + gap))
const length = data.length const length = data.length
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
if (i + 1 < unref(cols)) { if (i < unref(cols)) {
if (height || data[i][props.height as string]) { heights.value[i] = data[i][props.height as string]
// 使 filterData.value.push({
// data[i][props.height as string] ...data[i],
const itemHeight = isString(data[i][props.height as string]) top: 0,
? Number(data[i][props.height as string].replace(/[^0-9]/gi, '')) left: i * (width + gap)
: 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() } else {
// imgEl.src = data[i][props.src as string] //
// imgEl.onload = async () => { //
// // const itemEl = container.querySelector(`.${prefixCls}-item__${i}`) let minHeight = heights.value[0]
// const clientRect = itemEl?.getBoundingClientRect() let index = 0
// if (clientRect) { //
// heights.value[i] = clientRect?.height 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
} }
onMounted(() => { watch(
() => prop.data,
async () => {
await nextTick()
filterWaterfall() filterWaterfall()
},
{
immediate: true
}
)
onMounted(() => {
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> </script>
<template> <template>
<div :class="prefixCls" ref="wrapEl">
<div <div
v-for="(item, $index) in data" :class="[prefixCls, 'flex', 'justify-center', 'items-center']"
:class="`${prefixCls}-item__${$index}`" ref="wrapEl"
:key="`water-${$index}`"
:style="{ :style="{
width: `${width}px` height: `${wrapHeight + 40}px`
}" }"
> >
<img :src="item[props.src as string]" class="w-full block" alt="" srcset="" /> <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="" />
</div>
<div
ref="loadMore"
class="h-40px flex justify-center absolute w-full"
:style="{
top: `${wrapHeight + gap}px`
}"
>
{{ end ? endText : loadingText }}
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -32,7 +32,7 @@ axiosInstance.interceptors.response.use(
return res return res
}, },
(error: AxiosError) => { (error: AxiosError) => {
console.log('err' + error) // for debug console.log('err ' + error) // for debug
ElMessage.error(error.message) ElMessage.error(error.message)
return Promise.reject(error) return Promise.reject(error)
} }

View File

@ -11,29 +11,51 @@ const data = ref<any>([])
const getList = () => { const getList = () => {
const list: any = [] const list: any = []
for (let i = 0; i < 20; i++) { 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( list.push(
Mock.mock({ Mock.mock({
width,
height,
id: toAnyString(), 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] data.value = [...unref(data), ...list]
console.log('【data】', data.value) if (unref(data).length >= 60) {
end.value = true
}
} }
getList() getList()
const { t } = useI18n() const { t } = useI18n()
const loading = ref(false)
const end = ref(false)
const loadMore = () => {
loading.value = true
setTimeout(() => {
getList()
loading.value = false
}, 1000)
}
</script> </script>
<template> <template>
<ContentWrap :title="t('router.waterfall')"> <ContentWrap :title="t('router.waterfall')">
<Waterfall <Waterfall
:data="data" :data="data"
:loading="loading"
:end="end"
:props="{ :props="{
src: 'image_uri', src: 'image_uri',
height: 'height' height: 'height'
}" }"
@load-more="loadMore"
/> />
</ContentWrap> </ContentWrap>
</template> </template>