(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 = []; 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 renderTable(plans) { const colCount = 10; if (plans.length === 0) { tableBody.innerHTML = '

没有符合条件的方案

'; return; } tableBody.innerHTML = plans.map(function (plan) { 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(''); } 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() { const filtered = applyFilters(); renderTable(filtered); } 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(); }); fetch('/api/plans') .then(function (res) { return res.json(); }) .then(function (plans) { allPlans = plans; fillFilterOptions(); refresh(); }) .catch(function () { tableBody.innerHTML = '

加载失败,请刷新页面

'; }); })();