feat: 优化捐赠留言显示

This commit is contained in:
alger
2025-05-07 00:15:45 +08:00
parent 2f07550316
commit 3ca7e9a271
3 changed files with 195 additions and 230 deletions
+3 -1
View File
@@ -3,5 +3,7 @@ export default {
'Your donation will be used to support development and maintenance work, including but not limited to server maintenance, domain name renewal, etc.', 'Your donation will be used to support development and maintenance work, including but not limited to server maintenance, domain name renewal, etc.',
message: 'You can leave your email or github name when leaving a message.', message: 'You can leave your email or github name when leaving a message.',
refresh: 'Refresh List', refresh: 'Refresh List',
toDonateList: 'Buy me a coffee' toDonateList: 'Buy me a coffee',
title: 'Donation List',
noMessage: 'No Message'
}; };
+3 -1
View File
@@ -2,5 +2,7 @@ export default {
description: '您的捐赠将用于支持开发和维护工作,包括但不限于服务器维护、域名续费等。', description: '您的捐赠将用于支持开发和维护工作,包括但不限于服务器维护、域名续费等。',
message: '留言时可留下您的邮箱或 github名称。', message: '留言时可留下您的邮箱或 github名称。',
refresh: '刷新列表', refresh: '刷新列表',
toDonateList: '请我喝咖啡' toDonateList: '请我喝咖啡',
noMessage: '暂无留言',
title: '捐赠列表'
}; };
+170 -209
View File
@@ -1,6 +1,44 @@
<template> <template>
<div class="donation-container"> <div class="donation-container">
<div class="refresh-container"> <div class="qrcode-container">
<div class="description">
<p>{{ t('donation.description') }}</p>
<p>{{ t('donation.message') }}</p>
</div>
<div class="qrcode-grid">
<div class="qrcode-item">
<n-image
:src="alipay"
:alt="t('common.alipay')"
class="qrcode-image"
preview-disabled
/>
<span class="qrcode-label">{{ t('common.alipay') }}</span>
</div>
<div class="donate-button">
<n-button type="primary" @click="toDonateList">
<template #icon>
<i class="ri-cup-line"></i>
</template>
{{ t('donation.toDonateList') }}
</n-button>
</div>
<div class="qrcode-item">
<n-image
:src="wechat"
:alt="t('common.wechat')"
class="qrcode-image"
preview-disabled
/>
<span class="qrcode-label">{{ t('common.wechat') }}</span>
</div>
</div>
</div>
<div class="header-container">
<h3 class="section-title">{{ t('donation.title') }}</h3>
<n-button secondary round size="small" :loading="isLoading" @click="fetchDonors"> <n-button secondary round size="small" :loading="isLoading" @click="fetchDonors">
<template #icon> <template #icon>
<i class="ri-refresh-line"></i> <i class="ri-refresh-line"></i>
@@ -8,15 +46,13 @@
{{ t('donation.refresh') }} {{ t('donation.refresh') }}
</n-button> </n-button>
</div> </div>
<div class="donation-grid" :class="{ 'grid-expanded': isExpanded }"> <div class="donation-grid" :class="{ 'grid-expanded': isExpanded }">
<div <div
v-for="(donor, index) in displayDonors" v-for="donor in displayDonors"
:key="donor.id" :key="donor.id"
class="donation-card animate__animated" class="donation-card"
:class="getAnimationClass(index)" :class="{ 'no-message': !donor.message }"
:style="{ animationDelay: `${index * 0.1}s` }"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
> >
<div class="card-content"> <div class="card-content">
<div class="donor-avatar"> <div class="donor-avatar">
@@ -24,46 +60,45 @@
:src="donor.avatar" :src="donor.avatar"
:fallback-src="defaultAvatar" :fallback-src="defaultAvatar"
round round
size="large" class="avatar-img"
class="animate__animated animate__pulse animate__infinite avatar-img"
/> />
<div class="donor-badge" :class="getBadgeClass(donor.badge)">
{{ donor.badge }}
</div>
</div> </div>
<div class="donor-info"> <div class="donor-info">
<div class="donor-meta">
<div class="donor-name">{{ donor.name }}</div> <div class="donor-name">{{ donor.name }}</div>
<div class="donation-meta"> <div class="price-tag">{{ donor.amount }}</div>
<n-tag </div>
:type="getAmountTagType(donor.amount)" <div class="donation-date">{{ donor.date }}</div>
size="small" </div>
class="donation-amount animate__animated" </div>
round
bordered <!-- 有留言的情况 -->
<n-popover
v-if="donor.message"
trigger="hover"
placement="bottom"
:show-arrow="true"
:width="240"
> >
{{ donor.amount }}
</n-tag>
<span class="donation-date">{{ donor.date }}</span>
</div>
</div>
</div>
<div v-if="donor.message" class="donation-message">
<n-popover trigger="hover" placement="bottom">
<template #trigger> <template #trigger>
<div class="message-content"> <div class="donation-message">
<i class="ri-double-quotes-l quote-icon"></i> <i class="ri-double-quotes-l quote-icon"></i>
<div class="message-text">{{ donor.message }}</div> <span class="message-text">{{ donor.message }}</span>
<i class="ri-double-quotes-r quote-icon"></i> <i class="ri-double-quotes-r quote-icon"></i>
</div> </div>
</template> </template>
<div class="message-popup"> <div class="message-popover">
<i class="ri-double-quotes-l quote-icon"></i> <i class="ri-double-quotes-l quote-icon"></i>
{{ donor.message }} <span>{{ donor.message }}</span>
<i class="ri-double-quotes-r quote-icon"></i> <i class="ri-double-quotes-r quote-icon"></i>
</div> </div>
</n-popover> </n-popover>
<!-- 没有留言的情况显示占位符 -->
<div v-else class="donation-message-placeholder">
<i class="ri-emotion-line"></i>
<span>{{ t('donation.noMessage') }}</span>
</div> </div>
<div class="card-sparkles"></div>
</div> </div>
</div> </div>
@@ -75,40 +110,6 @@
{{ isExpanded ? t('common.collapse') : t('common.expand') }} {{ isExpanded ? t('common.collapse') : t('common.expand') }}
</n-button> </n-button>
</div> </div>
<div class="p-6 rounded-lg shadow-lg">
<div class="description text-center text-sm text-gray-700 dark:text-gray-200">
<p>{{ t('donation.description') }}</p>
<p>{{ t('donation.message') }}</p>
</div>
<div class="flex justify-between mt-6">
<div class="flex flex-col items-center gap-2">
<n-image
:src="alipay"
:alt="t('common.alipay')"
class="w-60 h-60 rounded-lg cursor-none"
preview-disabled
/>
<span class="text-sm text-gray-700 dark:text-gray-200">{{ t('common.alipay') }}</span>
</div>
<n-button type="primary" @click="toDonateList">
<template #icon>
<i class="ri-cup-line"></i>
</template>
{{ t('donation.toDonateList') }}
<i class="ri-arrow-right-s-line"></i>
</n-button>
<div class="flex flex-col items-center gap-2">
<n-image
:src="wechat"
:alt="t('common.wechat')"
class="w-60 h-60 rounded-lg cursor-none"
preview-disabled
/>
<span class="text-sm text-gray-700 dark:text-gray-200">{{ t('common.wechat') }}</span>
</div>
</div>
</div>
</div> </div>
</template> </template>
@@ -152,72 +153,9 @@ onActivated(() => {
fetchDonors(); fetchDonors();
}); });
// 动画类名列表 // 只按金额排序的捐赠列表
const animationClasses = [
'animate__fadeInUp',
'animate__fadeInLeft',
'animate__fadeInRight',
'animate__zoomIn'
];
// 获取随机动画类名
const getAnimationClass = (index: number) => {
return animationClasses[index % animationClasses.length];
};
// 根据金额获取标签类型
const getAmountTagType = (amount: number): 'success' | 'warning' | 'error' | 'info' => {
if (amount >= 5) return 'warning';
if (amount >= 2) return 'success';
return 'info';
};
// 获取徽章样式类名
const getBadgeClass = (badge: string): string => {
if (badge.includes('金牌')) return 'badge-gold';
if (badge.includes('银牌')) return 'badge-silver';
return 'badge-bronze';
};
// 鼠标悬停效果
const handleMouseEnter = (event: MouseEvent) => {
const card = event.currentTarget as HTMLElement;
card.style.transform = 'translateY(-2px)';
card.style.boxShadow = '0 8px 20px rgba(0, 0, 0, 0.12)';
// 添加金额标签动画
const amountTag = card.querySelector('.donation-amount');
if (amountTag) {
amountTag.classList.add('animate__tada');
}
};
const handleMouseLeave = (event: MouseEvent) => {
const card = event.currentTarget as HTMLElement;
card.style.transform = 'translateY(0)';
card.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.08)';
// 移除金额标签动画
const amountTag = card.querySelector('.donation-amount');
if (amountTag) {
amountTag.classList.remove('animate__tada');
}
};
// 按金额和留言排序的捐赠列表
const sortedDonors = computed(() => { const sortedDonors = computed(() => {
return [...donors.value].sort((a, b) => { return [...donors.value].sort((a, b) => b.amount - a.amount);
// 如果一个有留言一个没有,有留言的排在前面
if (a.message && !b.message) return -1;
if (!a.message && b.message) return 1;
// 都有留言或都没有留言时,按金额从大到小排序
const amountDiff = b.amount - a.amount;
if (amountDiff !== 0) return amountDiff;
// 金额相同时,按日期从新到旧排序
return new Date(b.date).getTime() - new Date(a.date).getTime();
});
}); });
const isExpanded = ref(false); const isExpanded = ref(false);
@@ -240,13 +178,21 @@ const toDonateList = () => {
<style lang="scss" scoped> <style lang="scss" scoped>
.donation-container { .donation-container {
@apply w-full overflow-hidden; @apply w-full overflow-hidden flex flex-col gap-4;
}
.header-container {
@apply flex justify-between items-center px-4 py-2;
.section-title {
@apply text-lg font-medium text-gray-700 dark:text-gray-200;
}
} }
.donation-grid { .donation-grid {
@apply grid gap-3 px-2 py-3 transition-all duration-300 overflow-hidden; @apply grid gap-3 transition-all duration-300 overflow-hidden;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
max-height: 280px; max-height: 320px;
@media (min-width: 768px) { @media (min-width: 768px) {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@@ -262,127 +208,142 @@ const toDonateList = () => {
} }
.donation-card { .donation-card {
@apply relative rounded-lg p-3 min-w-0 w-full transition-all duration-500 shadow-md backdrop-blur-md; @apply rounded-lg p-2.5 transition-all duration-200 hover:shadow-md;
@apply bg-gradient-to-br from-white/[0.03] to-white/[0.08] border border-white/[0.08]; @apply bg-light-100 dark:bg-gray-800/5 backdrop-blur-sm;
@apply hover:shadow-lg; @apply border border-gray-200 dark:border-gray-700/10;
@apply flex flex-col justify-between;
min-height: 100px;
&.no-message {
@apply justify-between;
}
.card-content { .card-content {
@apply relative z-10 flex items-start gap-3; @apply flex items-start gap-2 mb-2;
}
} }
.donor-avatar { .donor-avatar {
@apply relative flex-shrink-0 w-10 h-9 transition-transform duration-300; @apply relative flex-shrink-0;
.avatar-img { .avatar-img {
@apply border border-white/20 dark:border-gray-800/50 shadow-sm; @apply border border-gray-200 dark:border-gray-700/10 shadow-sm;
@apply w-10 h-9; @apply w-9 h-9;
} }
} }
.donor-badge {
@apply absolute -bottom-2 -right-1 px-1.5 py-0.5 text-xs font-medium text-white/90 rounded-full whitespace-nowrap;
@apply bg-gradient-to-r from-pink-400 to-pink-500 shadow-sm opacity-90 scale-90;
@apply transition-all duration-300;
}
.donor-info { .donor-info {
@apply flex-1 min-w-0; @apply flex-1 min-w-0 flex flex-col justify-center;
.donor-meta {
@apply flex justify-between items-center mb-0.5;
.donor-name { .donor-name {
@apply text-sm font-medium mb-0.5 truncate; @apply text-sm font-medium truncate flex-1 mr-1;
} }
.donation-meta { .price-tag {
@apply flex items-center gap-2 mb-1; @apply text-xs text-gray-400/80 dark:text-gray-500/80 whitespace-nowrap;
}
}
.donation-date { .donation-date {
@apply text-xs text-gray-400/80 dark:text-gray-500/80; @apply text-xs text-gray-400/60 dark:text-gray-500/60;
}
} }
} }
.donation-message { .donation-message {
@apply text-sm text-gray-600 dark:text-gray-300 leading-relaxed mt-3 w-full; @apply text-xs text-gray-500 dark:text-gray-400 italic mt-1 px-2 py-1.5;
@apply bg-gray-100/10 dark:bg-dark-300 rounded;
.message-content { @apply flex items-start;
@apply relative p-2 rounded-lg transition-all duration-300 cursor-pointer; @apply cursor-pointer transition-all duration-200;
@apply bg-white/[0.02] hover:bg-[var(--n-color)];
.message-text {
@apply px-6 italic line-clamp-2;
}
.quote-icon { .quote-icon {
@apply absolute text-gray-400/60 dark:text-gray-500/60 text-sm; @apply text-gray-300 dark:text-gray-600 flex-shrink-0 opacity-60;
&:first-child { &:first-child {
@apply left-0.5 top-2; @apply mr-1 self-start;
} }
&:last-child { &:last-child {
@apply right-0.5 bottom-2; @apply ml-1 self-end;
}
} }
} }
.message-text {
@apply flex-1 line-clamp-2;
} }
&:hover { &:hover {
.donor-avatar { @apply bg-gray-100/40 dark:bg-dark-200;
@apply scale-105 rotate-3;
}
.donor-badge {
@apply scale-95 -translate-y-0.5;
}
.card-sparkles {
@apply opacity-60 scale-110;
}
}
}
.card-sparkles {
@apply absolute inset-0 pointer-events-none opacity-0 transition-all duration-500;
background-image: radial-gradient(2px 2px at 20px 30px, rgba(255, 255, 255, 0.95), transparent),
radial-gradient(2px 2px at 40px 70px, rgba(255, 255, 255, 0.95), transparent),
radial-gradient(2.5px 2.5px at 50px 160px, rgba(255, 255, 255, 0.95), transparent),
radial-gradient(2px 2px at 90px 40px, rgba(255, 255, 255, 0.95), transparent),
radial-gradient(2.5px 2.5px at 130px 80px, rgba(255, 255, 255, 0.95), transparent);
background-size: 200% 200%;
animation: sparkle 8s ease infinite;
}
@keyframes sparkle {
0%,
100% {
@apply bg-[0%_0%] opacity-40 scale-100;
}
50% {
@apply bg-[100%_100%] opacity-80 scale-110;
} }
} }
.refresh-container { .donation-message-placeholder {
@apply flex justify-end px-2 py-2; @apply text-xs text-gray-400 dark:text-gray-500 mt-1 px-2 py-1.5;
@apply bg-gray-100/5 dark:bg-dark-300 rounded;
@apply flex items-center justify-center gap-1 italic;
@apply border border-transparent;
i {
@apply text-gray-300 dark:text-gray-600;
}
}
.message-popover {
@apply text-sm text-gray-700 dark:text-gray-200 italic p-2;
@apply flex items-start;
.quote-icon {
@apply text-gray-400 dark:text-gray-500 flex-shrink-0;
&:first-child {
@apply mr-1.5 self-start;
}
&:last-child {
@apply ml-1.5 self-end;
}
}
} }
.expand-button { .expand-button {
@apply flex justify-center items-center py-2; @apply flex justify-center items-center py-2;
:deep(.n-button) { :deep(.n-button) {
@apply transition-all duration-300 hover:-translate-y-0.5; @apply transition-all duration-200 hover:-translate-y-0.5;
} }
} }
.message-popup { .qrcode-container {
@apply relative px-4 py-2 text-sm; @apply p-5 rounded-lg shadow-sm bg-light-100 dark:bg-gray-800/5 backdrop-blur-sm border border-gray-200 dark:border-gray-700/10;
max-width: 300px;
line-height: 1.6;
font-style: italic;
.quote-icon { .description {
@apply text-gray-400/60 dark:text-gray-500/60; @apply text-center text-sm text-gray-600 dark:text-gray-300 mb-4;
font-size: 0.9rem;
p {
@apply mb-2;
}
}
.qrcode-grid {
@apply flex justify-between items-center gap-4;
.qrcode-item {
@apply flex flex-col items-center gap-2;
.qrcode-image {
@apply w-36 h-36 rounded-lg border border-gray-200 dark:border-gray-700/10 shadow-sm transition-transform duration-200 hover:scale-105;
}
.qrcode-label {
@apply text-sm text-gray-600 dark:text-gray-300;
}
}
.donate-button {
@apply flex flex-col items-center justify-center;
}
} }
} }
</style> </style>