mirror of
https://github.com/Coldsmiles/infstarweb.git
synced 2026-04-23 02:30:41 +08:00
feat: Enhance sponsor page with improved layout, search functionality, and dynamic filters
This commit is contained in:
@@ -1,7 +1,38 @@
|
||||
let allSponsors = [];
|
||||
let grandTotal = 0;
|
||||
let filterState = { search: '', project: 'all' };
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
fetchSponsorsData();
|
||||
setupListeners();
|
||||
});
|
||||
|
||||
function setupListeners() {
|
||||
const searchInput = document.getElementById('sponsor-search');
|
||||
const filterContainer = document.getElementById('project-filters');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
filterState.search = e.target.value.toLowerCase().trim();
|
||||
applyFilters();
|
||||
});
|
||||
}
|
||||
|
||||
if (filterContainer) {
|
||||
filterContainer.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('filter-tag')) {
|
||||
// Update active class
|
||||
document.querySelectorAll('.filter-tag').forEach(btn => btn.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
|
||||
// Update filter state
|
||||
filterState.project = e.target.dataset.project;
|
||||
applyFilters();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSponsorsData() {
|
||||
try {
|
||||
const response = await fetch('sponsors.txt');
|
||||
@@ -13,7 +44,8 @@ async function fetchSponsorsData() {
|
||||
|
||||
const sponsors = [];
|
||||
const userTotals = {};
|
||||
let grandTotal = 0;
|
||||
const projects = new Set();
|
||||
grandTotal = 0;
|
||||
|
||||
lines.forEach(line => {
|
||||
const parts = line.split(',');
|
||||
@@ -27,6 +59,7 @@ async function fetchSponsorsData() {
|
||||
if (!isNaN(amount)) {
|
||||
sponsors.push({ name, project, amount, date });
|
||||
grandTotal += amount;
|
||||
projects.add(project);
|
||||
|
||||
if (userTotals[name]) {
|
||||
userTotals[name] += amount;
|
||||
@@ -37,36 +70,117 @@ async function fetchSponsorsData() {
|
||||
}
|
||||
});
|
||||
|
||||
// Update Total
|
||||
const totalDisplay = document.getElementById('total-amount-display');
|
||||
if (totalDisplay) {
|
||||
totalDisplay.innerText = `累计获得赞助: ¥${grandTotal.toFixed(2)}`;
|
||||
}
|
||||
allSponsors = [...sponsors].reverse(); // Start with newest
|
||||
|
||||
// Recent Donations Logic
|
||||
// Original list is chronological (oldest to newest), reverse it for newest first
|
||||
const recentSponsors = [...sponsors].reverse();
|
||||
renderDonationGrid(recentSponsors);
|
||||
// Animate Total
|
||||
animateValue(grandTotal);
|
||||
|
||||
// Render everything
|
||||
renderFilters(Array.from(projects));
|
||||
applyFilters(); // Renders the grid initially
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading sponsors:', error);
|
||||
const grid = document.getElementById('donation-list');
|
||||
if(grid) grid.innerHTML = '<p>加载数据失败</p>';
|
||||
if(grid) grid.innerHTML = '<div style="text-align:center; padding: 40px; color: var(--text-secondary); grid-column: 1/-1;">加载数据失败,请刷新重试</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderDonationGrid(sponsors) {
|
||||
const container = document.getElementById('donation-list');
|
||||
function animateValue(end) {
|
||||
const obj = document.getElementById('total-amount-display');
|
||||
if (!obj) return;
|
||||
|
||||
// Simple count up
|
||||
let startTimestamp = null;
|
||||
const duration = 2000;
|
||||
const start = 0;
|
||||
|
||||
const step = (timestamp) => {
|
||||
if (!startTimestamp) startTimestamp = timestamp;
|
||||
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
|
||||
// Easing out Quart
|
||||
const easeProgress = 1 - Math.pow(1 - progress, 4);
|
||||
|
||||
const current = Math.floor(easeProgress * (end - start) + start);
|
||||
|
||||
obj.innerHTML = `¥${current.toLocaleString('en-US')}`;
|
||||
|
||||
if (progress < 1) {
|
||||
window.requestAnimationFrame(step);
|
||||
} else {
|
||||
obj.innerHTML = `¥${end.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2})}`;
|
||||
}
|
||||
};
|
||||
window.requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function renderFilters(projects) {
|
||||
const container = document.getElementById('project-filters');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = sponsors.map(s => `
|
||||
<div class="donation-card">
|
||||
<div class="donation-header">
|
||||
<span class="donor-name">${s.name}</span>
|
||||
<span class="donation-amount">¥${s.amount.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="donation-purpose">${s.project}</div>
|
||||
<div class="donation-date">${s.date}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
// Remove existing project buttons, keep "All"
|
||||
const existingButtons = container.querySelectorAll('button:not([data-project="all"])');
|
||||
existingButtons.forEach(btn => btn.remove());
|
||||
|
||||
// Add project buttons
|
||||
projects.forEach(proj => {
|
||||
if (!proj) return;
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'filter-tag';
|
||||
btn.textContent = proj;
|
||||
btn.dataset.project = proj;
|
||||
container.appendChild(btn);
|
||||
});
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
const { search, project } = filterState;
|
||||
const grid = document.getElementById('donation-list');
|
||||
const noResults = document.getElementById('no-results');
|
||||
|
||||
if (!grid) return;
|
||||
|
||||
const filtered = allSponsors.filter(item => {
|
||||
const matchesProject = project === 'all' || item.project === project;
|
||||
const matchesSearch = item.name.toLowerCase().includes(search);
|
||||
return matchesProject && matchesSearch;
|
||||
});
|
||||
|
||||
grid.innerHTML = '';
|
||||
|
||||
if (filtered.length === 0) {
|
||||
if (noResults) noResults.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
if (noResults) noResults.style.display = 'none';
|
||||
|
||||
filtered.forEach((item, index) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'donation-card';
|
||||
// Max delay 1s to prevent long waits on huge lists
|
||||
const delay = Math.min(index * 0.05, 1);
|
||||
card.style.animationDelay = `${delay}s`;
|
||||
|
||||
const avatarUrl = `https://minotar.net/helm/${item.name}/64.png`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="donation-header">
|
||||
<div class="donor-info">
|
||||
<img src="${avatarUrl}" class="mini-avatar" onerror="this.src='https://minotar.net/helm/MHF_Steve/64.png'" alt="${item.name}">
|
||||
<div class="donor-name">${item.name}</div>
|
||||
</div>
|
||||
<div class="donation-amount">¥${item.amount}</div>
|
||||
</div>
|
||||
|
||||
<div style="flex-grow: 1; display: flex; flex-direction: column;">
|
||||
<div class="donation-purpose">${item.project}</div>
|
||||
<div class="donation-date">
|
||||
<i class="far fa-clock" style="margin-right: 4px;"></i>${item.date}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user