(function () { const tableBody = document.getElementById('table-body'); const filterProvider = document.getElementById('filter-provider'); const filterRegion = document.getElementById('filter-region'); const filterMemory = document.getElementById('filter-memory'); const filterCurrency = document.getElementById('filter-currency'); const btnReset = document.getElementById('btn-reset'); let allPlans = []; let isLoading = false; function unique(values) { return [...new Set(values)].filter(Boolean).sort(); } function getDisplayRegion(plan) { return (plan.countries && plan.countries.trim()) ? plan.countries.trim() : (plan.region || "—"); } function getAllRegionOptions(plans) { const set = new Set(); plans.forEach(function (p) { if (p.countries) { p.countries.split(',').forEach(function (s) { const t = s.trim(); if (t) set.add(t); }); } if (p.region && p.region.trim()) set.add(p.region.trim()); }); return unique([...set]); } function fillFilterOptions() { const providers = unique(allPlans.map(function (p) { return p.provider; })); const regions = getAllRegionOptions(allPlans); filterProvider.innerHTML = ''; providers.forEach(function (p) { const opt = document.createElement('option'); opt.value = p; opt.textContent = p; filterProvider.appendChild(opt); }); filterRegion.innerHTML = ''; regions.forEach(function (r) { const opt = document.createElement('option'); opt.value = r; opt.textContent = r; filterRegion.appendChild(opt); }); } function formatPrice(plan) { const isCNY = filterCurrency.value === 'CNY'; let price = isCNY ? plan.price_cny : plan.price_usd; let symbol = isCNY ? '¥' : '$'; if (price == null || price === '' || isNaN(price)) { price = isCNY ? plan.price_usd : plan.price_cny; symbol = isCNY ? '$' : '¥'; } if (price == null || price === '' || isNaN(price)) return "—"; return symbol + Number(price).toLocaleString(); } function showVal(val, suffix) { if (val == null || val === '' || (typeof val === 'number' && isNaN(val))) return "—"; return suffix ? val + suffix : val; } function applyFilters() { const provider = filterProvider.value; const region = filterRegion.value; const memoryMin = parseInt(filterMemory.value, 10) || 0; return allPlans.filter(function (plan) { if (provider && plan.provider !== provider) return false; if (region) { const matchRegion = getDisplayRegion(plan) === region || (plan.countries && plan.countries.split(',').map(function (s) { return s.trim(); }).indexOf(region) !== -1) || plan.region === region; if (!matchRegion) return false; } if (memoryMin && (plan.memory_gb == null || plan.memory_gb < memoryMin)) return false; return true; }); } function showLoadingSkeleton() { const colCount = 10; const skeletonRows = Array(5).fill(0).map(function() { return ''; }).join(''); tableBody.innerHTML = skeletonRows; } function renderTable(plans) { const colCount = 10; if (plans.length === 0) { tableBody.innerHTML = '

🔍 没有符合条件的方案

'; return; } // 添加淡入动画 tableBody.style.opacity = '0'; tableBody.innerHTML = plans.map(function (plan, index) { const url = (plan.official_url || "").trim(); const linkCell = url ? '🔗 官网' : "—"; return ( '' + '' + escapeHtml(plan.provider) + '' + '' + escapeHtml(getDisplayRegion(plan)) + '' + '' + escapeHtml(plan.name) + '' + '' + showVal(plan.vcpu) + '' + '' + showVal(plan.memory_gb, ' GB') + '' + '' + showVal(plan.storage_gb, ' GB') + '' + '' + showVal(plan.bandwidth_mbps, ' Mbps') + '' + '' + (plan.traffic ? escapeHtml(plan.traffic) : '—') + '' + '' + formatPrice(plan) + '' + '' + linkCell + '' + '' ); }).join(''); // 触发淡入 setTimeout(function() { tableBody.style.opacity = '1'; }, 10); } function escapeAttr(s) { const div = document.createElement('div'); div.textContent = s; return div.innerHTML.replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>'); } function escapeHtml(s) { if (s == null || s === '') return ''; const div = document.createElement('div'); div.textContent = String(s); return div.innerHTML; } function refresh() { if (isLoading) return; const filtered = applyFilters(); renderTable(filtered); // 更新结果计数 updateResultCount(filtered.length); } function updateResultCount(count) { const existingCount = document.querySelector('.result-count'); if (existingCount) { existingCount.textContent = '共 ' + count + ' 条结果'; } } filterProvider.addEventListener('change', refresh); filterRegion.addEventListener('change', refresh); filterMemory.addEventListener('change', refresh); filterCurrency.addEventListener('change', refresh); btnReset.addEventListener('click', function () { filterProvider.value = ''; filterRegion.value = ''; filterMemory.value = '0'; filterCurrency.value = 'CNY'; refresh(); }); // 显示加载状态 isLoading = true; showLoadingSkeleton(); fetch('/api/plans') .then(function (res) { if (!res.ok) throw new Error('Network response was not ok'); return res.json(); }) .then(function (plans) { allPlans = plans; fillFilterOptions(); isLoading = false; refresh(); // 添加结果计数显示 const filters = document.querySelector('.filters'); if (filters && !document.querySelector('.result-count')) { const countEl = document.createElement('div'); countEl.className = 'result-count'; countEl.style.cssText = 'margin-left: auto; color: var(--text-muted); font-size: 0.9rem;'; countEl.textContent = '共 ' + plans.length + ' 条结果'; filters.appendChild(countEl); } }) .catch(function (error) { isLoading = false; console.error('加载失败:', error); tableBody.innerHTML = '

❌ 加载失败,请刷新页面重试

'; }); // 添加 CSS 动画 const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .table-wrap { transition: opacity 0.3s ease; } `; document.head.appendChild(style); })();