perf(页面加载): 优化价格显示性能并添加加载状态处理
- 改用DOMContentLoaded事件提前初始化页面 - 实现渐进式价格显示:基础价格→加载中→实时价格 - 为fetchVisitData添加超时和重试机制 - 新增价格状态视觉区分样式 - 从2分钟等待优化为立即显示基础价格,后台异步更新
This commit is contained in:
@@ -158,6 +158,12 @@ const BOOKED_STATUS = {
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
### 2025-01-27
|
### 2025-01-27
|
||||||
|
- 🚀 **页面加载性能优化**:
|
||||||
|
- 改用 `DOMContentLoaded` 事件替代 `window.onload`,提前初始化页面
|
||||||
|
- 实现渐进式价格显示:立即显示基础价格 → 加载状态 → 实时价格
|
||||||
|
- 新增 `fetchVisitData()` 超时处理(15秒)和重试机制(3次)
|
||||||
|
- 新增价格状态视觉区分:基础价格(灰色斜体)、加载中(蓝色动画)、实时价格(绿色加粗)
|
||||||
|
- 优化用户体验:从首次加载需2分钟等待优化为立即显示基础价格,后台异步更新实时价格
|
||||||
- 🔧 **新增Token自动更新功能**:
|
- 🔧 **新增Token自动更新功能**:
|
||||||
- 添加了Umami token自动获取和更新机制
|
- 添加了Umami token自动获取和更新机制
|
||||||
- 新增 `getNewUmamiToken()` 函数用于获取新token
|
- 新增 `getNewUmamiToken()` 函数用于获取新token
|
||||||
|
180
script.js
180
script.js
@@ -187,17 +187,57 @@ function initAdPositions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取访问量数据
|
// 获取访问量数据
|
||||||
async function fetchVisitData() {
|
/**
|
||||||
try {
|
* 获取访问量数据(带超时和重试机制)
|
||||||
const response = await fetch('info.php');
|
* @param {number} timeout 超时时间(毫秒)
|
||||||
if (!response.ok) {
|
* @param {number} retries 重试次数
|
||||||
throw new Error('获取访问量数据失败');
|
* @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);
|
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() {
|
function updateAllAdPrices() {
|
||||||
console.log('开始更新价格...');
|
console.log('开始更新实时价格...');
|
||||||
try {
|
try {
|
||||||
Object.entries(AD_POSITIONS).forEach(([adId, adConfig]) => {
|
Object.entries(AD_POSITIONS).forEach(([adId, adConfig]) => {
|
||||||
const prices = adConfig.prices;
|
const prices = adConfig.prices;
|
||||||
@@ -229,6 +323,8 @@ function updateAllAdPrices() {
|
|||||||
if (monthlyElement) {
|
if (monthlyElement) {
|
||||||
const actualPrice = calculateActualPrice(prices.monthly, visitData.last_month_pv);
|
const actualPrice = calculateActualPrice(prices.monthly, visitData.last_month_pv);
|
||||||
monthlyElement.textContent = actualPrice.toLocaleString();
|
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) {
|
if (yearlyElement) {
|
||||||
const actualPrice = calculateActualPrice(prices.yearly, visitData.last_month_pv);
|
const actualPrice = calculateActualPrice(prices.yearly, visitData.last_month_pv);
|
||||||
yearlyElement.textContent = actualPrice.toLocaleString();
|
yearlyElement.textContent = actualPrice.toLocaleString();
|
||||||
|
yearlyElement.classList.remove('base-price', 'loading-price');
|
||||||
|
yearlyElement.classList.add('real-time-price');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log('实时价格更新完成');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新价格时出错:', 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();
|
async function initializePage() {
|
||||||
updateAllAdPrices();
|
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 () => {
|
setInterval(async () => {
|
||||||
await fetchVisitData();
|
console.log('定期更新访问量数据...');
|
||||||
updateAllAdPrices();
|
const success = await fetchVisitData(10000, 1);
|
||||||
|
if (success) {
|
||||||
|
updateAllAdPrices();
|
||||||
|
}
|
||||||
}, 5 * 60 * 1000);
|
}, 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();
|
||||||
|
}
|
||||||
|
});
|
42
styles.css
42
styles.css
@@ -48,6 +48,46 @@ main {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
gap: 2rem;
|
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;
|
margin-bottom: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,4 +505,4 @@ footer {
|
|||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
Reference in New Issue
Block a user