画廊-瀑布流实现

This commit is contained in:
2025-08-11 17:06:29 +08:00
parent 0438a2dcfb
commit 22ca45510b
9 changed files with 256 additions and 20 deletions
+178 -8
View File
@@ -1,20 +1,190 @@
<template>
<div class="gallery-page">
<h1>画廊页</h1>
<div class="test w-100 ml-20 mb-20">
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
<PerfectScrollbar ref="scrollbar" @ps-scroll-y="handleScroll">
<div class="gallery-page py-5 px-[10%]">
<d-tabs v-model="tid" type="slider">
<d-tab id="share" title="画廊">
<div class="gallery-container w-full box-border">
<!-- 瀑布流容器 -->
<div ref="waterfallContainer" v-image-preview
class="waterfall-container flex justify-between flex-nowrap w-full overflow-hidden">
<!-- 动态生成的列 -->
<div v-for="(column, index) in columns" :key="index" class="waterfall-column flex flex-col w-[240px]">
<div v-for="item in column" :key="item.id"
class="gallery-item my-[10px] rounded-lg overflow-hidden transition-transform duration-300 box-border hover:-translate-y-1.5">
<img :src="item.filepath" alt="" class="gallery-image block w-full h-auto object-cover rounded-md">
</div>
</div>
</div>
<!-- 加载中指示器 -->
<div v-if="loading" class="loading-indicator text-center p-5 text-gray-600">加载中...</div>
</div>
</d-tab>
<d-tab id="my" title="我的">我的</d-tab>
</d-tabs>
</div>
<!-- 画廊内容 -->
</div>
</PerfectScrollbar>
</template>
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
// 画廊页逻辑
const tid = ref('share');
const fileList = ref<any[]>([]);
const pn = ref(1);
const ps = ref(40);
const loading = ref(false);
const waterfallContainer = ref<HTMLDivElement | null>(null);
const columns = ref<Array<Array<any>>>([]);
const columnHeights = ref<number[]>([]);
const columnCount = ref(4); // 默认列数
const imageHeights = ref<Record<string, number>>({}); // 存储每张图片的实际高度
const imagesLoaded = ref(0);
const itemWidth = ref(240); // 图片宽度固定为220px
const scrollbar = ref<any>(null);
// 计算列数 based on 屏幕宽度
function calculateColumnCount() {
if (!waterfallContainer.value) return;
const containerWidth = waterfallContainer.value.clientWidth;
const newColumnCount = Math.max(1, Math.floor(containerWidth / itemWidth.value));
if (newColumnCount !== columnCount.value) {
columnCount.value = newColumnCount;
resetWaterfall();
}
}
// 重置瀑布流
function resetWaterfall() {
columns.value = Array(columnCount.value).fill(0).map(() => []);
columnHeights.value = Array(columnCount.value).fill(0);
// 重新分配图片
fileList.value.forEach(item => addToWaterfall(item));
}
// 添加图片到瀑布流
function addToWaterfall(item: any) {
if (columns.value.length === 0) return;
// 找到高度最小的列
let minHeight = Math.min(...columnHeights.value);
let minIndex = columnHeights.value.indexOf(minHeight);
// 添加到该列
columns.value[minIndex].push(item);
// 估算列高 - 实际高度会在图片加载后更新
const estimatedHeight = itemWidth.value; // 假设1:1比例
columnHeights.value[minIndex] += estimatedHeight + 24; // 加上padding和margin
}
// 图片加载完成后更新瀑布流
function onImageLoad(id: string) {
// 等待DOM更新
nextTick(() => {
if (!waterfallContainer.value) return;
const imgElements = waterfallContainer.value.querySelectorAll(`.gallery-image[src*="${id}"]`);
if (imgElements.length > 0) {
const img = imgElements[0] as HTMLImageElement;
imageHeights.value[id] = img.height;
updateColumnHeights();
}
});
}
// 更新所有列的高度
function updateColumnHeights() {
if (!waterfallContainer.value) return;
const columnElements = waterfallContainer.value.querySelectorAll('.waterfall-column');
columnHeights.value = Array.from(columnElements).map(el => el.clientHeight);
}
// 获取文件列表
async function getFileList() {
if (loading.value) return;
loading.value = true;
try {
const res = await $http.file.getFileList({
page_num: pn.value,
page_size: ps.value,
});
console.log('>>> --> getFileList --> res:', res);
if (pn.value === 1) {
fileList.value = res.data;
resetWaterfall();
} else {
// 追加新数据
res.data.forEach((item: any) => {
fileList.value.push(item);
addToWaterfall(item);
});
}
} catch (error) {
console.error('获取文件列表失败:', error);
} finally {
loading.value = false;
}
}
// 监听滚动事件
function handleScroll(e: any) {
// console.log('>>> --> handleScroll --> loading:', e)
if (loading.value) return;
const scrollTop = e.target.scrollTop
// console.log('>>> --> handleScroll --> scrollTop:', scrollTop)
const scrollHeight = e.target.scrollHeight
// console.log('>>> --> handleScroll --> scrollHeight:', scrollHeight)
const clientHeight = e.target.offsetHeight;
// 当滚动到距离底部20%时加载更多
// console.log('>>> --> clientHeight --> clientHeight:', clientHeight)
if (scrollTop + clientHeight >= scrollHeight * 0.8) {
console.log('>>> --> handleScroll --> 加载更多')
pn.value++;
getFileList();
}
}
onMounted(() => {
getFileList();
// 计算初始列数
calculateColumnCount();
// 添加窗口大小变化监听
window.addEventListener('resize', calculateColumnCount);
// 添加滚动监听
window.addEventListener('scroll', handleScroll);
});
onUnmounted(() => {
// 移除监听
window.removeEventListener('resize', calculateColumnCount);
window.removeEventListener('scroll', handleScroll);
});
</script>
<style scoped lang="less">
/* 画廊页样式 */
.test {
box-shadow: 0 2px 12px 0 @primary !important;
:deep(.devui-tabs__nav) {
display: flex;
justify-content: center;
width: 100% !important;
// padding: 0 10%;
li {
width: 50%;
display: flex;
justify-content: center;
}
li a span {
font-size: 18px !important;
font-weight: 500;
}
}
.ps {
height: calc(100vh - 65px);
width: 100%;
}
</style>