优化组件类型声明,添加页面描述元信息,更新依赖项,添加新图片,重构多个组件,改进画廊功能

This commit is contained in:
2025-12-18 16:00:15 +08:00
parent 1034786c40
commit 7b313a2264
12 changed files with 201 additions and 301 deletions
+82 -246
View File
@@ -1,119 +1,45 @@
<template>
<PerfectScrollbar ref="scrollbar" @ps-scroll-y="handleScroll">
<div ref="myCon" class="gallery-page py-5 px-[10%]">
<d-tabs v-model="tid" type="options" :before-change="beforeChange" @active-tab-change.native="activeTabChange">
<d-tab id="share" title="画廊">
<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>
<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 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>
</d-tab>
<d-tab id="my" title="我的">
<div>
<d-upload class="py-8 upload-demo border-2 border-dashed border-gray-300 rounded-md p-4"
:on-success="handleSuccess" :upload-options="uploadOptions" droppable="true">
<div class="w-full h-full flex flex-col items-center justify-center">
<div><d-icon name="upload" size="32px"></d-icon></div>
<div class="my-2">
将文件拖到此处
<span class="devui-link text-[#ec66ab]">点击上传</span>
</div>
<div class="text-sm text-gray-500">注意图片大小不要大于5MB哟~</div>
</div>
</d-upload>
</div>
<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="px-2 truncate absolute 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="pr-2 absolute bottom-0 flex justify-between items-center w-full bg-[#00000060]">
<div class="text-white ">
<span class="px-2 py-1 bg-primary rounded-full" v-if="item.share">已共享</span>
<span class="px-2 py-1 bg-[#ffa500] rounded-full" v-else>未共享</span>
</div>
<div class="icon flex items-center">
<d-popover content="下载" trigger="hover" class="!bg-primary" style="color: #fff">
<icon-download @click="downloadFile(item.filepath)"
class="w-5 h-5 mr-2 text-white hover-text-primary"></icon-download>
</d-popover>
<d-popover v-if="item.share" content="取消共享" trigger="hover" class="!bg-primary"
style="color: #fff">
<icon-lock @click="handdleShare(item.pid, item.share)"
class="w-5 h-5 text-white hover-text-primary"></icon-lock>
</d-popover>
<d-popover v-else content="共享" trigger="hover" class="!bg-primary" style="color: #fff">
<icon-unlock @click="handdleShare(item.pid, item.share)"
class="w-5 h-5 text-white hover-text-primary"></icon-unlock>
</d-popover>
</div>
</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>
</d-tab>
</d-tabs>
</div>
<Teleport to="body">
<div>
<img class="fixed top-10 -left-2" :src="tao" alt="">
<img class="fixed bottom-10 left-0 w-44" :src="sword" alt="">
<img class="fixed bottom-0 right-5 w-25" :src="psword" alt="">
<img class="fixed top-[56px] right-0" :src="denglong" alt="">
</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>
</Teleport>
</div>
</PerfectScrollbar>
</template>
<script setup lang="ts">
import psword from "@/assets/images/剑-粉.png";
import sword from "@/assets/images/剑.png";
import tao from "@/assets/images/树枝桃.png";
import denglong from "@/assets/images/灯笼.png";
import { throttle } from 'es-toolkit';
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
import { onMounted, onUnmounted, ref } from 'vue';
definePage({
name:'gallery',
name: 'gallery',
meta: {
title: '画廊',
}
@@ -121,7 +47,6 @@ definePage({
// 画廊页逻辑
const tid = ref('share');
const fileList = ref<any[]>([]);
const pn = ref(1);
const ps = ref(40);
@@ -130,23 +55,17 @@ 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);
const itemWidth = ref(240); // 图片宽度固定为240px
const kw = ref<string>('');
const myCon = ref<HTMLDivElement | null>(null);
const uploadOptions = ref({
uri: 'https://www.hxyouzi.com/api/files/upload',
method: 'POST',
maximumSize: 5 * 1024 * 1024,
headers: {
'Authorization': 'Bearer ' + $cookies.get('token'),
},
});
// 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() {
@@ -164,64 +83,46 @@ function resetWaterfall() {
columns.value = Array(columnCount.value).fill(0).map(() => []);
columnHeights.value = Array(columnCount.value).fill(0);
// 重新分配图片
fileList.value.forEach(item => addToWaterfall(item));
fileList.value.forEach(async item =>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)
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 // 假设1:1比例
const estimatedHeight = itemWidth.value * height / width
columnHeights.value[minIndex] += estimatedHeight + 20; // 加上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 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(() => {
if (image.width > 0 && image.height > 0) {
height = image.height
width = image.width
resolve({ height, width })
clearTimeout(timer)
}
}, 40)
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)
});
}
// 更新所有列的高度
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;
@@ -251,33 +152,33 @@ async function getFileList() {
}
}
// 获取我的文件列表
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);
// // 获取我的文件列表
// 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;
}
}
// 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) => {
@@ -302,42 +203,6 @@ function onSearch() {
getFileList();
}
function beforeChange(tabId: string) {
myCon.value?.scrollIntoView(true)
if (tabId === 'my') {
// 我的画廊,不加载更多
if (!$cookies.get('token')) {
$msg.error('请先登录');
return false
}
return true
}
return true
}
function activeTabChange(tabId: string) {
pn.value = 1;
kw.value = '';
fileList.value = [];
resetWaterfall();
if (tabId === 'my') {
// console.log('>>> --> activeTabChange --> tabId:', tabId)
getMyList()
} else if (tabId === 'share') {
getFileList();
}
}
function handleSuccess(res: any) {
console.log('>>> --> handleSuccess --> res:', res)
const code = JSON.parse(res[0].response).code
if (code === 200) {
$msg.success('上传成功');
getMyList();
}
}
function downloadFile(url: string) {
console.log('>>> --> downloadFile --> url:', url)
// 创建临时a标签
@@ -358,35 +223,6 @@ function downloadFile(url: string) {
document.body.removeChild(link);
}
async function handdleShare(id: string, share: boolean | number) {
console.log('>>> --> handdleShare --> id:', id)
console.log('>>> --> handdleShare --> share:', share)
try {
const res = await $http.file.putShare({
pid: id,
share: Number(!share),
})
console.log('>>> --> handdleShare --> res:', res)
if (res.code === 200) {
$msg.success('共享成功');
pn.value = 1;
fileList.value = [];
resetWaterfall();
getMyList();
console.log('>>> --> handdleShare --> myCon.value:', myCon.value?.scrollTop)
if (myCon.value) {
myCon.value.scrollIntoView(true)
}
}
} catch (error) {
console.error('共享文件失败:', error);
}
}
onMounted(() => {
getFileList();
// 计算初始列数