perf(页面加载): 优化价格显示性能并添加加载状态处理
- 改用DOMContentLoaded事件提前初始化页面 - 实现渐进式价格显示:基础价格→加载中→实时价格 - 为fetchVisitData添加超时和重试机制 - 新增价格状态视觉区分样式 - 从2分钟等待优化为立即显示基础价格,后台异步更新
This commit is contained in:
@@ -158,6 +158,12 @@ const BOOKED_STATUS = {
|
||||
|
||||
## 更新日志
|
||||
### 2025-01-27
|
||||
- 🚀 **页面加载性能优化**:
|
||||
- 改用 `DOMContentLoaded` 事件替代 `window.onload`,提前初始化页面
|
||||
- 实现渐进式价格显示:立即显示基础价格 → 加载状态 → 实时价格
|
||||
- 新增 `fetchVisitData()` 超时处理(15秒)和重试机制(3次)
|
||||
- 新增价格状态视觉区分:基础价格(灰色斜体)、加载中(蓝色动画)、实时价格(绿色加粗)
|
||||
- 优化用户体验:从首次加载需2分钟等待优化为立即显示基础价格,后台异步更新实时价格
|
||||
- 🔧 **新增Token自动更新功能**:
|
||||
- 添加了Umami token自动获取和更新机制
|
||||
- 新增 `getNewUmamiToken()` 函数用于获取新token
|
||||
|
174
script.js
174
script.js
@@ -187,17 +187,57 @@ function initAdPositions() {
|
||||
}
|
||||
|
||||
// 获取访问量数据
|
||||
async function fetchVisitData() {
|
||||
/**
|
||||
* 获取访问量数据(带超时和重试机制)
|
||||
* @param {number} timeout 超时时间(毫秒)
|
||||
* @param {number} retries 重试次数
|
||||
* @returns {Promise<boolean>} 是否成功获取数据
|
||||
*/
|
||||
async function fetchVisitData(timeout = 10000, retries = 2) {
|
||||
for (let attempt = 0; attempt <= retries; attempt++) {
|
||||
try {
|
||||
const response = await fetch('info.php');
|
||||
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('获取访问量数据失败');
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
visitData = await response.json();
|
||||
console.log('访问量数据已更新:', visitData);
|
||||
|
||||
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.error('获取访问量数据时出错:', 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)));
|
||||
}
|
||||
}
|
||||
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分钟更新一次访问量数据
|
||||
setInterval(async () => {
|
||||
await fetchVisitData();
|
||||
// 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 () => {
|
||||
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();
|
||||
}
|
||||
});
|
40
styles.css
40
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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user