整体优化

This commit is contained in:
2026-08-04 17:33:45 +08:00
parent cadc8885f3
commit 2c69b3f1c3
32 changed files with 2212 additions and 643 deletions
+111 -28
View File
@@ -13,7 +13,7 @@
<n-scrollbar ref="virtualListInst" class="relative z-[1] py-6" :style="boxStyle" @scroll="handleScroll">
<section
class="relative mx-auto w-[82%] rounded-[28px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] p-6 shadow-[0_20px_50px_rgba(110,124,112,0.11)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']">
class="relative mx-auto w-[82%] rounded-[28px] border border-white/62 bg-[linear-gradient(145deg,rgba(255,251,246,0.68),rgba(237,246,244,0.56))] p-6 shadow-[0_1px_3px_rgba(97,121,146,0.05),0_16px_38px_rgba(97,121,146,0.09),0_44px_84px_rgba(178,128,88,0.07),inset_0_1px_0_rgba(255,255,255,0.7)] backdrop-blur-[20px] after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(120deg,rgba(255,255,255,0.24),transparent_45%)] after:content-['']">
<div class="max-w-[48rem]">
<h1 class="m-0 text-[clamp(1.8rem,2.5vw,2.8rem)] leading-[1.2] text-[#2c3a4a]">
把喜欢的画面收进一片更柔和的光里
@@ -24,11 +24,11 @@
</div>
<div
class="mt-6 rounded-full border border-white/62 bg-[linear-gradient(135deg,rgba(255,249,241,0.76),rgba(236,245,243,0.72)),radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_34%)] shadow-[inset_0_1px_0_rgba(255,255,255,0.55)]">
<n-input class="gallery-search" round size="large" v-model:value="kw" @keyup.enter="onSearch"
placeholder="输入关键词,按回车开始搜索">
class="mt-6 rounded-full border border-white/62 bg-[linear-gradient(135deg,rgba(255,249,241,0.76),rgba(236,245,243,0.72)),radial-gradient(circle_at_top_right,rgba(255,255,255,0.28),transparent_34%)] shadow-[inset_0_1px_0_rgba(255,255,255,0.55)] transition-[box-shadow,border-color] duration-300">
<n-input class="gallery-search" round size="large" clearable v-model:value="kw" @keyup.enter="onSearch"
placeholder="输入关键词,按回车开始搜索">
<template #suffix>
<n-icon class="cursor-pointer text-slate-500" size="large">
<n-icon class="cursor-pointer text-slate-500 transition-colors duration-200 hover:text-primary" size="large" @click="onSearch">
<icon-search />
</n-icon>
</template>
@@ -37,43 +37,62 @@
</section>
<section
class="mx-auto mt-6 w-[82%] rounded-[30px] border border-white/62 bg-white/42 p-6 shadow-[0_18px_42px_rgba(109,126,117,0.1)] backdrop-blur-[18px]">
class="mx-auto mt-6 w-[82%] rounded-[30px] border border-white/62 bg-white/42 p-6 shadow-[0_1px_3px_rgba(97,121,146,0.05),0_14px_30px_rgba(97,121,146,0.09),0_36px_72px_rgba(178,128,88,0.07),inset_0_1px_0_rgba(255,255,255,0.7)] backdrop-blur-[18px]">
<div class="mb-5 flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div>
<div class="text-[1.6rem] font-semibold text-[#2f4050]">图片列表</div>
<div class="mt-1 flex items-center gap-2 text-[13px] text-slate-500">
<span v-if="searchKeyword" class="inline-flex items-center rounded-full bg-[rgba(236,102,171,0.1)] px-2.5 py-0.5 font-semibold text-[rgb(212,88,154)]">
关键词{{ searchKeyword }}
</span>
<span>{{ galleryStats.status }}</span>
</div>
</div>
<div
class="inline-flex items-center whitespace-nowrap rounded-full border border-white/80 bg-white/68 px-4 py-[0.65rem] text-[#526376]">
<div class="inline-flex items-center gap-2 self-start rounded-full bg-white/68 px-5 py-2 text-[#526376] shadow-[0_2px_8px_rgba(97,121,146,0.08),inset_0_1px_0_rgba(255,255,255,0.7)]">
<span class="inline-block h-2 w-2 rounded-full" :class="loading ? 'animate-pulse bg-[#ec66ab]' : 'bg-[#7fb69a]'"></span>
{{ galleryStats.loaded }}
</div>
</div>
<Waterfall ref="waterfall" :list="fileList" :gutter="gutter" :columns="column" class="w-full" img-selector="url"
<Waterfall ref="waterfall" :list="fileList" :gutter="gutter" :columns="column" row-key="filepath" class="w-full" img-selector="url"
animation-effect="fadeIn" :animation-duration="1000" :animation-delay="300" backgroundColor="transparent">
<template #item="{ item }">
<div
class="group relative flex h-full items-center justify-center overflow-hidden rounded-[24px] border border-white/82 bg-[linear-gradient(180deg,rgba(255,252,248,0.92),rgba(239,244,241,0.78))] shadow-[0_16px_34px_rgba(116,132,124,0.14)] backdrop-blur-[14px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:border-white/196 hover:shadow-[0_22px_42px_rgba(102,123,112,0.18)]">
<LazyImg class="overflow-hidden rounded-[20px]" :Pwidth="item.width" :Pheight="item.height"
:url="item.filepath" />
class="gallery-card group relative flex h-full items-center justify-center overflow-hidden rounded-[12px] border border-white/82 bg-[linear-gradient(180deg,rgba(255,252,248,0.92),rgba(239,244,241,0.78))] backdrop-blur-[14px] transition-[transform,box-shadow,border-color] duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] hover:-translate-y-1">
<LazyImg class="gallery-img overflow-hidden rounded-[12px]" :Pwidth="item.width" :Pheight="item.height"
:url="item.thumbnail" :preUrl="item.filepath" />
<div
class="absolute left-0 right-0 top-0 z-10 overflow-hidden text-ellipsis whitespace-nowrap rounded-[14px] border border-white/22 bg-[rgba(39,50,65,0.1)] px-3 py-1 items-center justify-center text-sm text-white hidden backdrop-blur-[12px] group-hover:flex">
class="pointer-events-none absolute left-0 right-0 top-0 z-10 truncate rounded-[12px] border-b border-white/22 bg-gradient-to-b from-[rgba(39,50,65,0.55)] to-transparent px-3 pb-3 pt-1.5 text-sm text-white opacity-0 backdrop-blur-[6px] transition-opacity duration-300 group-hover:opacity-100">
{{ item.filename }}
</div>
<div
class="absolute bottom-0 left-0 right-0 z-10 flex items-center justify-between gap-3 rounded-[14px] border border-white/22 bg-[rgba(39,50,65,0.5)] px-3 py-1 text-white backdrop-blur-[12px]">
class="absolute bottom-0 left-0 right-0 z-10 flex translate-y-[102%] items-center justify-between gap-3 rounded-[12px] border border-white/22 bg-[rgba(39,50,65,0.55)] px-3 py-1.5 text-white backdrop-blur-[12px] transition-transform duration-[320ms] [transition-timing-function:cubic-bezier(0.22,1,0.36,1)] group-hover:translate-y-0">
<div class="min-w-0 overflow-hidden text-ellipsis whitespace-nowrap text-sm text-[#eef4f8]">
<span class="font-semibold text-[#ffd8cb]">{{ item.nickname }}</span> 分享
</div>
<div
class="inline-flex flex-shrink-0 cursor-pointer items-center rounded-full bg-white/18 px-4 py-1 text-[0.86rem] font-semibold text-[#ffe3d3] transition-colors duration-200 hover:bg-white/24 hover:text-white"
@click="downloadFile(item.filepath)">
<icon-download class="mr-1 h-5 w-5" />
class="inline-flex flex-shrink-0 cursor-pointer items-center rounded-full bg-white/18 px-3.5 py-1 text-[0.86rem] font-semibold text-[#ffe3d3] transition-[background-color,transform] duration-200 hover:scale-105 hover:bg-white/28 hover:text-white active:scale-95"
@click.stop="downloadFile(item.filepath)">
<icon-download class="mr-1 h-4 w-4" />
下载
</div>
</div>
</div>
</template>
</Waterfall>
<div v-if="fileList.length === 0 && !loading" class="flex flex-col items-center justify-center py-16 text-slate-400">
<icon-search class="mb-3 h-10 w-10 opacity-50" />
<p class="text-[15px]">{{ searchKeyword ? '未找到相关图片,换个关键词试试' : '暂无图片' }}</p>
</div>
<div v-if="loading" class="flex items-center justify-center gap-2 py-6 text-sm text-slate-400">
<icon-loading class="zhuan h-5 w-5 text-primary" />
<span>{{ fileList.length ? '加载中...' : '首次加载中...' }}</span>
</div>
<div v-else-if="isLoadAll && fileList.length" class="py-5 text-center text-[13px] text-slate-400">
已加载全部 {{ fileList.length }} 张图片
</div>
</section>
</n-scrollbar>
</div>
@@ -120,6 +139,7 @@ const cwidth = ref(240)
const column = ref(5)
const gutter = ref(18)
const isLoadAll = ref(false)
let requestSeq = 0
const searchKeyword = computed(() => kw.value.trim())
@@ -180,6 +200,7 @@ function resetGalleryState() {
async function getFileList() {
if (loading.value) return
loading.value = true
const seq = ++requestSeq
try {
const res = await $http.file.getFileList({
@@ -189,6 +210,9 @@ async function getFileList() {
})
const list = res.data as GalleryItem[]
// 过期请求直接丢弃,避免搜索时旧结果覆盖新结果
if (seq !== requestSeq) return
if (list.length < ps.value) {
isLoadAll.value = true
}
@@ -197,8 +221,10 @@ async function getFileList() {
} catch (error) {
console.error('获取图片列表失败:', error)
} finally {
loading.value = false
renderWaterfall()
if (seq === requestSeq) {
loading.value = false
renderWaterfall()
}
}
}
@@ -216,12 +242,17 @@ const handleScroll = throttle((e: Event) => {
pn.value += 1
getFileList()
}
}, 1000)
}, 300)
function onSearch() {
pn.value = 1
kw.value = kw.value.trim()
isLoadAll.value = false
// 立即清空旧列表,避免搜索时看到上一批结果
fileList.value = []
// 若正在加载中,先解除守卫,让新搜索请求可以发起
loading.value = false
requestSeq += 1
getFileList()
}
@@ -240,16 +271,17 @@ onMounted(() => {
calculateColumns()
updateBoxStyle()
getFileList()
window.addEventListener('resize', calculateColumns)
window.addEventListener('resize', updateBoxStyle)
window.addEventListener('scroll', handleScroll)
window.addEventListener('resize', handleResize)
})
function handleResize() {
calculateColumns()
updateBoxStyle()
}
onUnmounted(() => {
resetGalleryState()
window.removeEventListener('resize', calculateColumns)
window.removeEventListener('resize', updateBoxStyle)
window.removeEventListener('scroll', handleScroll)
window.removeEventListener('resize', handleResize)
})
// 防抖函数
@@ -260,8 +292,14 @@ onUnmounted(() => {
:deep(.gallery-search .n-input) {
background: rgba(255, 255, 255, 0.74) !important;
border: 1px solid rgba(255, 255, 255, 0.7) !important;
box-shadow: 0 10px 28px rgba(109, 138, 132, 0.1) !important;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.6),
0 2px 6px rgba(97, 121, 146, 0.05),
0 10px 24px rgba(97, 121, 146, 0.09) !important;
backdrop-filter: blur(18px);
transition:
border-color 0.25s ease,
box-shadow 0.25s ease;
}
:deep(.gallery-search .n-input__input-el) {
@@ -274,7 +312,52 @@ onUnmounted(() => {
:deep(.gallery-search .n-input.n-input--focus) {
border-color: rgba(122, 160, 148, 0.7) !important;
box-shadow: 0 16px 38px rgba(118, 149, 139, 0.16) !important;
box-shadow:
0 0 0 4px rgba(122, 160, 148, 0.12),
inset 0 1px 0 rgba(255, 255, 255, 0.6),
0 2px 6px rgba(97, 121, 146, 0.06),
0 16px 36px rgba(97, 121, 146, 0.14) !important;
}
/* 图片卡片 hover 增强 */
.gallery-card {
box-shadow:
0 1px 3px rgba(97, 121, 146, 0.05),
0 12px 26px rgba(97, 121, 146, 0.09),
0 32px 60px rgba(178, 128, 88, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.72) !important;
}
.gallery-card:hover {
border-color: rgba(255, 255, 255, 0.88) !important;
box-shadow:
0 2px 6px rgba(97, 121, 146, 0.06),
0 18px 38px rgba(97, 121, 146, 0.14),
0 48px 90px rgba(178, 128, 88, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.8) !important;
}
.gallery-img {
transition: transform 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
.gallery-card:hover .gallery-img {
transform: scale(1.045);
}
/* 加载图标旋转 */
.zhuan {
animation: gallery-spin 1.5s linear infinite;
}
@keyframes gallery-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
:deep(.n-image img) {