迁移UI框架至naive-ui,重构组件和样式,添加Gallery和Mask组件
This commit is contained in:
281
src/components/Gallery.vue
Normal file
281
src/components/Gallery.vue
Normal file
@ -0,0 +1,281 @@
|
||||
<template>
|
||||
<PerfectScrollbar ref="scrollbar" @ps-scroll-y="handleScroll">
|
||||
<div ref="myCon" class="gallery-page py-5 px-[10%]">
|
||||
<d-search class="mt-0 mb-8 w-2/3 mx-auto rounded-full" v-model="kw" is-keyup-search :delay="1000"
|
||||
@search="onSearch"></d-search>
|
||||
<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 group relative my-[10px] rounded-lg overflow-hidden transition-transform duration-300 box-border hover:-translate-y-1.5">
|
||||
<div
|
||||
class="absolute px-2 truncate hidden group-hover:block top-0 text-center w-full bg-[#00000070] text-white">
|
||||
{{ item.filename }}</div>
|
||||
<img :src="item.filepath" alt="" class="gallery-image block w-full h-auto object-cover rounded-md">
|
||||
<div class="px-2 absolute bottom-0 flex justify-between w-full bg-[#00000060]">
|
||||
<div class="text-white ">由 <span class="text-[#f1d9db] font-600">{{ item.nickname }}</span> 上传</div>
|
||||
<d-popover content="下载" trigger="hover" class="!bg-primary" style="color: #fff">
|
||||
<icon-download @click="downloadFile(item.filepath)"
|
||||
class="w-5 h-5 text-white hover-text-primary"></icon-download>
|
||||
</d-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 加载中指示器 -->
|
||||
<div v-if="loading" class="loading-indicator text-center p-5 text-gray-600">加载中...</div>
|
||||
<div v-else class="loading-indicator text-center p-5 text-gray-600">已全部加载完成</div>
|
||||
</div>
|
||||
</div>
|
||||
</PerfectScrollbar>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { throttle } from 'es-toolkit';
|
||||
|
||||
definePage({
|
||||
name: 'gallery',
|
||||
meta: {
|
||||
title: '画廊',
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 画廊页逻辑
|
||||
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 itemWidth = ref(240); // 图片宽度固定为240px
|
||||
const kw = ref<string>('');
|
||||
|
||||
// const uploadOptions = ref({
|
||||
// uri: 'https://www.hxyouzi.com/api/files/upload',
|
||||
// method: 'POST',
|
||||
// maximumSize: 5 * 1024 * 1024,
|
||||
// headers: {
|
||||
// 'Authorization': 'Bearer ' + $cookies.get('token'),
|
||||
// },
|
||||
// });
|
||||
|
||||
// 计算列数 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(async (item:any) =>await addToWaterfall(item));
|
||||
console.log('>>> --> resetWaterfall --> fileList:', fileList.value)
|
||||
}
|
||||
|
||||
// 添加图片到瀑布流
|
||||
async function addToWaterfall(item: any) {
|
||||
if (columns.value.length === 0) return;
|
||||
const { height, width } =await getImageSizeByCheck(item.filepath)
|
||||
|
||||
// 找到高度最小的列
|
||||
let minHeight = Math.min(...columnHeights.value);
|
||||
let minIndex = columnHeights.value.indexOf(minHeight);
|
||||
console.log('>>> --> addToWaterfall --> item:', item)
|
||||
console.log('>>> --> addToWaterfall --> minIndex:', minIndex)
|
||||
// 添加到该列
|
||||
columns.value[minIndex].push(item);
|
||||
// 估算列高 - 实际高度会在图片加载后更新
|
||||
const estimatedHeight = itemWidth.value * height / width
|
||||
columnHeights.value[minIndex] += estimatedHeight + 20; // 加上padding和margin
|
||||
}
|
||||
|
||||
function getImageSizeByCheck(url: string): any {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let image = new Image();
|
||||
image.src = url;
|
||||
let height = 0
|
||||
let width = 0
|
||||
// let timer = setTimeout(() => {
|
||||
image.onload = () => {
|
||||
if (image.width > 0 && image.height > 0) {
|
||||
height = image.height
|
||||
width = image.width
|
||||
resolve({ height, width })
|
||||
// clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
// }, 100)
|
||||
});
|
||||
}
|
||||
|
||||
// 获取文件列表
|
||||
async function getFileList() {
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await $http.file.getFileList({
|
||||
keyword: kw.value,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// // 获取我的文件列表
|
||||
// async function getMyList() {
|
||||
// if (loading.value) return;
|
||||
// loading.value = true;
|
||||
// try {
|
||||
// const res = await $http.file.getMyList({
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
// 监听滚动事件
|
||||
const handleScroll: any = throttle((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();
|
||||
}
|
||||
}, 1000)
|
||||
function onSearch() {
|
||||
pn.value = 1;
|
||||
getFileList();
|
||||
}
|
||||
|
||||
function downloadFile(url: string) {
|
||||
console.log('>>> --> downloadFile --> url:', url)
|
||||
// 创建临时a标签
|
||||
const link = document.createElement('a');
|
||||
// 设置下载链接
|
||||
link.href = url;
|
||||
// 提取文件名
|
||||
const fileName = url.split('/').pop() || 'downloaded-file';
|
||||
// 设置下载属性和文件名
|
||||
link.download = fileName;
|
||||
// 设置为隐藏元素
|
||||
link.style.display = 'none';
|
||||
// 添加到文档
|
||||
document.body.appendChild(link);
|
||||
// 触发点击事件
|
||||
link.click();
|
||||
// 移除临时元素
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getFileList();
|
||||
// 计算初始列数
|
||||
calculateColumnCount();
|
||||
// 添加窗口大小变化监听
|
||||
window.addEventListener('resize', calculateColumnCount);
|
||||
// 添加滚动监听
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
pn.value = 1;
|
||||
kw.value = '';
|
||||
fileList.value = [];
|
||||
resetWaterfall();
|
||||
|
||||
// 移除监听
|
||||
window.removeEventListener('resize', calculateColumnCount);
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/* 画廊页样式 */
|
||||
: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%;
|
||||
}
|
||||
|
||||
:deep(.devui-upload) {
|
||||
width: 100%;
|
||||
|
||||
&>div {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user