diff --git a/readme.md b/readme.md index 2905be7..cbcf3df 100644 --- a/readme.md +++ b/readme.md @@ -158,6 +158,12 @@ const BOOKED_STATUS = { ## 更新日志 ### 2025-01-27 +- 🚀 **页面加载性能优化**: + - 改用 `DOMContentLoaded` 事件替代 `window.onload`,提前初始化页面 + - 实现渐进式价格显示:立即显示基础价格 → 加载状态 → 实时价格 + - 新增 `fetchVisitData()` 超时处理(15秒)和重试机制(3次) + - 新增价格状态视觉区分:基础价格(灰色斜体)、加载中(蓝色动画)、实时价格(绿色加粗) + - 优化用户体验:从首次加载需2分钟等待优化为立即显示基础价格,后台异步更新实时价格 - 🔧 **新增Token自动更新功能**: - 添加了Umami token自动获取和更新机制 - 新增 `getNewUmamiToken()` 函数用于获取新token diff --git a/script.js b/script.js index e64d35e..a6169a5 100644 --- a/script.js +++ b/script.js @@ -187,17 +187,57 @@ function initAdPositions() { } // 获取访问量数据 -async function fetchVisitData() { - try { - const response = await fetch('info.php'); - if (!response.ok) { - throw new Error('获取访问量数据失败'); +/** + * 获取访问量数据(带超时和重试机制) + * @param {number} timeout 超时时间(毫秒) + * @param {number} retries 重试次数 + * @returns {Promise} 是否成功获取数据 + */ +async function fetchVisitData(timeout = 10000, retries = 2) { + for (let attempt = 0; attempt <= retries; attempt++) { + try { + console.log(`正在获取访问量数据... (尝试 ${attempt + 1}/${retries + 1})`); + + // 创建带超时的fetch请求 + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeout); + + const response = await fetch('info.php', { + signal: controller.signal, + cache: 'no-cache' + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + + // 验证数据完整性 + if (data && typeof data.last_month_pv === 'number') { + visitData = data; + console.log('访问量数据获取成功:', visitData); + return true; + } else { + throw new Error('返回数据格式不正确'); + } + + } catch (error) { + console.warn(`获取访问量数据失败 (尝试 ${attempt + 1}/${retries + 1}):`, error.message); + + // 如果是最后一次尝试,记录错误但不抛出异常 + if (attempt === retries) { + console.error('所有重试均失败,将使用基础价格'); + return false; + } + + // 等待一段时间后重试 + await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1))); } - visitData = await response.json(); - console.log('访问量数据已更新:', visitData); - } catch (error) { - console.error('获取访问量数据时出错:', error); } + return false; } // 根据访问量获取价格系数 @@ -216,9 +256,63 @@ function calculateActualPrice(basePrice, monthlyPV) { return Math.round(basePrice * coefficient); } -// 更新所有广告位的价格显示 +/** + * 显示基础价格(不依赖API数据) + */ +function showBasePrices() { + console.log('显示基础价格...'); + Object.entries(AD_POSITIONS).forEach(([adId, adConfig]) => { + const prices = adConfig.prices; + + // 显示月付基础价格 + if (prices.monthly) { + const monthlyElement = document.getElementById(`${adId}-monthly`); + if (monthlyElement) { + monthlyElement.textContent = prices.monthly.toLocaleString(); + monthlyElement.classList.add('base-price'); + } + } + + // 显示年付基础价格 + if (prices.yearly) { + const yearlyElement = document.getElementById(`${adId}-yearly`); + if (yearlyElement) { + yearlyElement.textContent = prices.yearly.toLocaleString(); + yearlyElement.classList.add('base-price'); + } + } + }); +} + +/** + * 显示价格加载状态 + */ +function showPriceLoading() { + console.log('显示价格加载状态...'); + Object.entries(AD_POSITIONS).forEach(([adId, adConfig]) => { + const prices = adConfig.prices; + + if (prices.monthly) { + const monthlyElement = document.getElementById(`${adId}-monthly`); + if (monthlyElement) { + monthlyElement.innerHTML = '计算中...'; + } + } + + if (prices.yearly) { + const yearlyElement = document.getElementById(`${adId}-yearly`); + if (yearlyElement) { + yearlyElement.innerHTML = '计算中...'; + } + } + }); +} + +/** + * 更新所有广告位的价格显示(实时价格) + */ function updateAllAdPrices() { - console.log('开始更新价格...'); + console.log('开始更新实时价格...'); try { Object.entries(AD_POSITIONS).forEach(([adId, adConfig]) => { const prices = adConfig.prices; @@ -229,6 +323,8 @@ function updateAllAdPrices() { if (monthlyElement) { const actualPrice = calculateActualPrice(prices.monthly, visitData.last_month_pv); monthlyElement.textContent = actualPrice.toLocaleString(); + monthlyElement.classList.remove('base-price', 'loading-price'); + monthlyElement.classList.add('real-time-price'); } } @@ -238,11 +334,16 @@ function updateAllAdPrices() { if (yearlyElement) { const actualPrice = calculateActualPrice(prices.yearly, visitData.last_month_pv); yearlyElement.textContent = actualPrice.toLocaleString(); + yearlyElement.classList.remove('base-price', 'loading-price'); + yearlyElement.classList.add('real-time-price'); } } }); + console.log('实时价格更新完成'); } catch (error) { console.error('更新价格时出错:', error); + // 如果更新失败,回退到基础价格 + showBasePrices(); } } @@ -367,15 +468,54 @@ document.querySelector('.contact-form').addEventListener('submit', function(e) { }); // 确保页面完全加载后再执行 -window.onload = async function() { - console.log('页面加载完成,开始初始化...'); - initAdPositions(); - await fetchVisitData(); - updateAllAdPrices(); +/** + * 页面初始化函数 + */ +async function initializePage() { + console.log('开始初始化页面...'); - // 每5分钟更新一次访问量数据 + // 1. 立即初始化广告位和显示基础价格 + initAdPositions(); + showBasePrices(); + console.log('基础价格显示完成'); + + // 2. 显示加载状态并异步获取实时数据 + setTimeout(() => { + showPriceLoading(); + + // 异步获取访问量数据并更新价格 + fetchVisitData(15000, 3).then(success => { + if (success) { + updateAllAdPrices(); + } else { + // 如果获取失败,回退到基础价格 + console.log('API获取失败,显示基础价格'); + showBasePrices(); + } + }); + }, 100); // 短暂延迟确保基础价格先显示 + + // 3. 设置定期更新(每5分钟) setInterval(async () => { - await fetchVisitData(); - updateAllAdPrices(); + console.log('定期更新访问量数据...'); + const success = await fetchVisitData(10000, 1); + if (success) { + updateAllAdPrices(); + } }, 5 * 60 * 1000); -}; \ No newline at end of file +} + +// 使用DOMContentLoaded而不是window.onload,提前初始化 +document.addEventListener('DOMContentLoaded', function() { + console.log('DOM加载完成,开始初始化...'); + initializePage(); +}); + +// 保留window.onload作为备用(防止DOMContentLoaded未触发) +window.addEventListener('load', function() { + // 检查是否已经初始化过 + if (!document.querySelector('.ad-positions').hasChildNodes()) { + console.log('备用初始化触发...'); + initializePage(); + } +}); \ No newline at end of file diff --git a/styles.css b/styles.css index dacf657..e615a6c 100644 --- a/styles.css +++ b/styles.css @@ -48,6 +48,46 @@ main { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 2rem; +} + +/* 价格状态样式 */ +.base-price { + color: #666 !important; + font-style: italic; + position: relative; +} + +.base-price::after { + content: " (基础价格)"; + font-size: 0.8em; + color: #999; + font-weight: normal; +} + +.loading-price { + color: #007bff !important; + animation: pulse 1.5s ease-in-out infinite; + font-weight: 500; +} + +@keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.6; } + 100% { opacity: 1; } +} + +.real-time-price { + color: #28a745 !important; + font-weight: bold; + position: relative; +} + +.real-time-price::after { + content: " (实时价格)"; + font-size: 0.8em; + color: #28a745; + font-weight: normal; + opacity: 0.8; margin-bottom: 3rem; } @@ -465,4 +505,4 @@ footer { font-size: 0.9em; font-weight: normal; margin-top: 4px; -} \ No newline at end of file +} \ No newline at end of file