perf(页面加载): 优化价格显示性能并添加加载状态处理

- 改用DOMContentLoaded事件提前初始化页面
- 实现渐进式价格显示:基础价格→加载中→实时价格
- 为fetchVisitData添加超时和重试机制
- 新增价格状态视觉区分样式
- 从2分钟等待优化为立即显示基础价格,后台异步更新
This commit is contained in:
2025-08-17 14:49:06 +08:00
parent 9653a70dad
commit 9e8a88274d
3 changed files with 207 additions and 21 deletions

View File

@@ -158,6 +158,12 @@ const BOOKED_STATUS = {
## 更新日志
### 2025-01-27
- 🚀 **页面加载性能优化**
- 改用 `DOMContentLoaded` 事件替代 `window.onload`,提前初始化页面
- 实现渐进式价格显示:立即显示基础价格 → 加载状态 → 实时价格
- 新增 `fetchVisitData()` 超时处理15秒和重试机制3次
- 新增价格状态视觉区分:基础价格(灰色斜体)、加载中(蓝色动画)、实时价格(绿色加粗)
- 优化用户体验从首次加载需2分钟等待优化为立即显示基础价格后台异步更新实时价格
- 🔧 **新增Token自动更新功能**
- 添加了Umami token自动获取和更新机制
- 新增 `getNewUmamiToken()` 函数用于获取新token

180
script.js
View File

@@ -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<boolean>} 是否成功获取数据
*/
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 = '<span class="loading-price">计算中...</span>';
}
}
if (prices.yearly) {
const yearlyElement = document.getElementById(`${adId}-yearly`);
if (yearlyElement) {
yearlyElement.innerHTML = '<span class="loading-price">计算中...</span>';
}
}
});
}
/**
* 更新所有广告位的价格显示(实时价格)
*/
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);
};
}
// 使用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();
}
});

View File

@@ -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;
}