Skip to content

Commit

Permalink
fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
unilei committed Dec 2, 2024
1 parent 015788a commit 7e9b99b
Show file tree
Hide file tree
Showing 26 changed files with 512 additions and 243 deletions.
233 changes: 195 additions & 38 deletions components/home/DoubanImageBox.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,93 @@
<script setup>
import placeHolderImage from '~/assets/placeholder.webp'
import placeHolderImage from "~/assets/placeholder.webp";
defineProps({
doubanData: {
type: Array,
default: () => [],
},
})
defineProps({
doubanData: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['goDouban'])
const emit = defineEmits(["goDouban"]);
// 图片加载状态管理
const imageLoadStatus = ref({})
// 图片加载状态管理
const imageLoadStatus = ref({});
// 获取代理图片URL
const getProxyImageUrl = url => {
if (!url) return placeHolderImage
return `/api/image-proxy?url=${encodeURIComponent(url)}`
}
// 获取代理图片URL
const getProxyImageUrl = (url) => {
if (!url) return placeHolderImage;
return `/api/image-proxy?url=${encodeURIComponent(url)}`;
};
// 处理图片加载完成
const handleImageLoad = (movieId) => {
imageLoadStatus.value[movieId] = "loaded";
};
// 处理图片加载完成
const handleImageLoad = movieId => {
imageLoadStatus.value[movieId] = 'loaded'
// 处理图片加载失败
const handleImageError = (movieId, event) => {
imageLoadStatus.value[movieId] = "error";
// 尝试重新加载一次
if (!event.target.dataset.retried) {
event.target.dataset.retried = "true";
setTimeout(() => {
event.target.src = getProxyImageUrl(event.target.dataset.originalSrc);
}, 1000);
}
};
// 图片观察器
const imageObserver = ref(null);
const imageRefs = ref({});
// 初始化Intersection Observer
onMounted(() => {
imageObserver.value = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const movieId = entry.target.dataset.movieId;
entry.target.src = getProxyImageUrl(entry.target.dataset.originalSrc);
imageObserver.value.unobserve(entry.target);
}
});
},
{
rootMargin: "50px 0px",
threshold: 0.01,
}
);
});
// 处理图片加载失败
const handleImageError = (movieId, event) => {
imageLoadStatus.value[movieId] = 'error'
event.target.src = placeHolderImage
onUnmounted(() => {
if (imageObserver.value) {
imageObserver.value.disconnect();
}
});
const goDouban = movie => {
emit('goDouban', movie)
// 设置图片引用并开始观察
const setImageRef = (el, movieId, originalSrc) => {
if (el && imageObserver.value) {
imageRefs.value[movieId] = el;
el.dataset.movieId = movieId;
el.dataset.originalSrc = originalSrc;
imageObserver.value.observe(el);
}
};
const colorMode = useColorMode();
const goDouban = (movie) => {
emit("goDouban", movie);
};
</script>

<template>
<div v-for="(item, i) in doubanData" :key="i" class="mx-5 xl:max-w-[1200px] xl:mx-auto my-10">
<div
v-for="(item, i) in doubanData"
:key="i"
class="mx-5 xl:max-w-[1200px] xl:mx-auto my-10"
>
<h1
class="flex flex-row items-center text-sm sm:text-base text-gray-700 font-bold dark:text-white mt-[20px] mb-4 group"
>
Expand All @@ -51,10 +102,13 @@
class="w-1 h-5 bg-red-400 rounded-full group-hover:h-6 transition-all duration-300 delay-150"
></span>
</div>
<span class="hover:text-blue-500 transition-colors duration-300 cursor-pointer">{{
item.name
}}</span>
<span class="ml-2 text-xs text-gray-400 dark:text-gray-500">{{ item.data.length }} 部</span>
<span
class="hover:text-blue-500 transition-colors duration-300 cursor-pointer"
>{{ item.name }}</span
>
<span class="ml-2 text-xs text-gray-400 dark:text-gray-500"
>{{ item.data.length }} 部</span
>
</h1>

<div
Expand All @@ -81,30 +135,133 @@

<!-- 图片 -->
<img
:src="getProxyImageUrl(movie.cover)"
:alt="movie.title"
loading="lazy"
class="w-full aspect-[3/4] object-cover transition-opacity duration-300"
:ref="(el) => setImageRef(el, `${item.name}-${index}`, movie.cover)"
:src="placeHolderImage"
class="w-full h-[180px] lg:h-[220px] xl:h-44 object-cover transition-all duration-300 group-hover:scale-105"
:class="{
'opacity-0': !imageLoadStatus[`${item.name}-${index}`],
'opacity-100': imageLoadStatus[`${item.name}-${index}`] === 'loaded',
'opacity-100 blur-0':
imageLoadStatus[`${item.name}-${index}`] === 'loaded',
'blur-sm': imageLoadStatus[`${item.name}-${index}`] === 'loading',
}"
loading="lazy"
decoding="async"
@load="handleImageLoad(`${item.name}-${index}`)"
@error="handleImageError(`${item.name}-${index}`, $event)"
:alt="movie.title"
referrerpolicy="no-referrer"
/>
<!-- 评分标签 -->
<div
v-if="
movie.rate &&
imageLoadStatus[`${item.name}-${index}`] === 'loaded'
"
class="absolute top-2 right-2 flex items-center gap-1 px-1.5 py-0.5 bg-black/50 backdrop-blur rounded-md transform transition-all duration-300 group-hover:scale-110 group-hover:bg-black/70"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-3 h-3 text-yellow-400"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
/>
</svg>
<span class="text-white text-xs font-medium">{{ movie.rate }}</span>
</div>
<!-- 加载失败显示 -->
<div
v-if="imageLoadStatus[`${item.name}-${index}`] === 'error'"
class="absolute inset-0 flex items-center justify-center bg-gray-100 dark:bg-gray-700"
>
<div class="text-center p-3">
<el-icon class="text-gray-400 mb-2" :size="24">
<PictureFilled />
</el-icon>
<p class="text-xs text-gray-500">暂无图片</p>
</div>
</div>

<!-- 悬停遮罩 -->
<div
class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent opacity-0 group-hover:opacity-100 transition-all duration-300"
>
<div class="absolute bottom-0 left-0 right-0 p-3">
<p class="text-white text-sm font-medium mb-1 line-clamp-2">
{{ movie.title }}
</p>
<div class="flex items-center gap-2">
<span
v-if="movie.rate"
class="flex items-center gap-1 text-yellow-400 text-xs font-bold"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-3 h-3"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
/>
</svg>
{{ movie.rate }}
</span>
<span class="text-gray-300 text-xs">{{ movie.year }}</span>
</div>
</div>
</div>

<!-- 悬停遮罩 -->
</div>

<div class="p-2">
<h3
class="text-sm font-medium text-gray-900 dark:text-gray-100 truncate group-hover:text-blue-500 transition-colors duration-300"
<p
class="text-sm text-center truncate dark:text-gray-100 font-medium group-hover:text-blue-500 transition-colors duration-300"
>
{{ movie.title }}
</h3>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
</p>
<p
class="text-xs text-center text-gray-500 dark:text-gray-400 mt-0.5"
>
{{ movie.year }}
</p>
</div>
</div>
</div>
</div>
</template>

<style scoped>
.animate-fadeIn {
animation: fadeIn 0.8s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
</style>
4 changes: 3 additions & 1 deletion pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ onMounted(async () => {
</script>

<template>
<div class="custom-bg min-h-screen py-[60px] transition-colors duration-300">
<div
class="custom-bg py-[60px] min-h-[calc(100vh-120px)] transition-colors duration-300"
>
<div
class="flex flex-col items-center justify-center gap-4 mt-[60px] animate-fadeIn"
>
Expand Down
Loading

0 comments on commit 7e9b99b

Please sign in to comment.