web异常待修复:屏蔽统计

This commit is contained in:
Alex Yang
2025-11-24 13:26:01 +08:00
parent ce4a8d8127
commit a59f655769
6 changed files with 430 additions and 23 deletions

View File

@@ -1,3 +1,11 @@
// 全局变量
let domainDataCache = {
blocked: null,
resolved: null
};
let domainUpdateTimer = null;
const DOMAIN_UPDATE_INTERVAL = 5000; // 域名排行更新间隔设为5秒比统计数据更新慢一些
// 初始化仪表盘面板
function initDashboardPanel() {
// 初始化小型图表
@@ -10,6 +18,8 @@ function initDashboardPanel() {
if (typeof startRealTimeUpdate === 'function') {
startRealTimeUpdate();
}
// 启动域名排行的独立更新
startDomainUpdate();
}
// 加载仪表盘数据
@@ -17,11 +27,50 @@ function loadDashboardData() {
// 加载统计卡片数据
updateStatCards();
// 加载最常屏蔽的域名
loadTopBlockedDomains();
// 首次加载时获取域名排行数据
if (!domainDataCache.blocked) {
loadTopBlockedDomains();
}
if (!domainDataCache.resolved) {
loadTopResolvedDomains();
}
}
// 启动域名排行的独立更新
function startDomainUpdate() {
if (domainUpdateTimer) {
clearInterval(domainUpdateTimer);
}
// 加载最常解析的域名
loadTopResolvedDomains();
// 立即执行一次更新
updateDomainRankings();
// 设置定时器
domainUpdateTimer = setInterval(() => {
// 仅当当前面板是仪表盘时更新数据
if (document.getElementById('dashboard') && document.getElementById('dashboard').classList.contains('active')) {
updateDomainRankings();
}
}, DOMAIN_UPDATE_INTERVAL);
}
// 停止域名排行更新
function stopDomainUpdate() {
if (domainUpdateTimer) {
clearInterval(domainUpdateTimer);
domainUpdateTimer = null;
}
}
// 更新域名排行数据
function updateDomainRankings() {
// 使用Promise.all并行加载提高效率
Promise.all([
loadTopBlockedDomains(true),
loadTopResolvedDomains(true)
]).catch(error => {
console.error('更新域名排行数据失败:', error);
});
}
// 更新统计卡片数据
@@ -634,17 +683,43 @@ function renderRequestsPieChart(labels, data) {
});
}
// 辅助函数:深度比较两个对象是否相等
function isEqual(obj1, obj2) {
// 处理null或undefined情况
if (obj1 === obj2) return true;
if (obj1 == null || obj2 == null) return false;
// 确保都是数组
if (!Array.isArray(obj1) || !Array.isArray(obj2)) return false;
if (obj1.length !== obj2.length) return false;
// 比较数组中每个元素
for (let i = 0; i < obj1.length; i++) {
const a = obj1[i];
const b = obj2[i];
// 比较域名和计数
if (a.domain !== b.domain || a.count !== b.count) {
return false;
}
}
return true;
}
// 加载最常屏蔽的域名
function loadTopBlockedDomains() {
function loadTopBlockedDomains(isUpdate = false) {
// 首先获取表格元素并显示加载状态
const topBlockedTable = document.getElementById('top-blocked-table');
const tbody = topBlockedTable ? topBlockedTable.querySelector('tbody') : null;
if (tbody) {
// 非更新操作时显示加载状态
if (tbody && !isUpdate) {
// 显示加载中状态
tbody.innerHTML = `<td colspan="100%" style="color: #7f8c8d; font-style: italic;">加载中...</td>`;
}
apiRequest('/top-blocked')
return apiRequest('/top-blocked')
.then(data => {
// 处理多种可能的数据格式,特别优化对用户提供格式的支持
let processedData = [];
@@ -681,7 +756,16 @@ function loadTopBlockedDomains() {
});
}
smoothRenderTable('top-blocked-table', processedData, renderDomainRow);
// 数据变化检测
const hasDataChanged = !isEqual(domainDataCache.blocked, processedData);
// 只在数据发生变化或不是更新操作时重新渲染
if (hasDataChanged || !isUpdate) {
// 更新缓存
domainDataCache.blocked = JSON.parse(JSON.stringify(processedData));
// 渲染最常屏蔽的域名表格
smoothRenderTable('top-blocked-table', processedData, renderDomainRow);
}
})
.catch(error => {
console.error('获取最常屏蔽域名失败:', error);
@@ -699,8 +783,18 @@ function loadTopBlockedDomains() {
}
// 加载最常解析的域名
function loadTopResolvedDomains() {
apiRequest('/top-resolved')
function loadTopResolvedDomains(isUpdate = false) {
// 首先获取表格元素
const topResolvedTable = document.getElementById('top-resolved-table');
const tbody = topResolvedTable ? topResolvedTable.querySelector('tbody') : null;
// 非更新操作时显示加载状态
if (tbody && !isUpdate) {
// 显示加载中状态
tbody.innerHTML = `<td colspan="100%" style="color: #7f8c8d; font-style: italic;">加载中...</td>`;
}
return apiRequest('/top-resolved')
.then(data => {
// 处理多种可能的数据格式
let processedData = [];
@@ -737,7 +831,16 @@ function loadTopResolvedDomains() {
});
}
smoothRenderTable('top-resolved-table', processedData, renderDomainRow);
// 数据变化检测
const hasDataChanged = !isEqual(domainDataCache.resolved, processedData);
// 只在数据发生变化或不是更新操作时重新渲染
if (hasDataChanged || !isUpdate) {
// 更新缓存
domainDataCache.resolved = JSON.parse(JSON.stringify(processedData));
// 渲染最常解析的域名表格
smoothRenderTable('top-resolved-table', processedData, renderDomainRow);
}
})
.catch(error => {
console.error('获取最常解析域名失败:', error);
@@ -803,8 +906,13 @@ function smoothRenderTable(tableId, newData, rowRenderer) {
const tbody = table ? table.querySelector('tbody') : null;
if (!tbody) return;
// 添加过渡类用于CSS动画支持
tbody.classList.add('table-transition');
if (!newData || newData.length === 0) {
showEmpty(tbody, '暂无数据记录');
// 移除过渡类
setTimeout(() => tbody.classList.remove('table-transition'), 300);
return;
}
@@ -838,6 +946,12 @@ function smoothRenderTable(tableId, newData, rowRenderer) {
// 更新数据属性
existingRow.dataset.count = count;
// 添加高亮效果用于CSS过渡
existingRow.classList.add('table-row-highlight');
setTimeout(() => {
existingRow.classList.remove('table-row-highlight');
}, 1000);
// 如果计数变化,应用平滑更新
if (oldCount !== count) {
const countCell = existingRow.querySelector('.count-cell');
@@ -854,6 +968,8 @@ function smoothRenderTable(tableId, newData, rowRenderer) {
// 新数据项,创建新行
const newRow = rowRenderer(item, index);
if (newRow) {
// 添加淡入动画类
newRow.classList.add('table-row-fade-in');
// 先设置透明度为0避免在错误位置闪烁
newRow.style.opacity = '0';
newRows.push(newRow);
@@ -867,7 +983,7 @@ function smoothRenderTable(tableId, newData, rowRenderer) {
const key = row.dataset.domain || row.querySelector('td:first-child').textContent;
if (!updatedRows.has(key)) {
// 添加淡出动画
row.classList.add('fade-out');
row.classList.add('table-row-fade-out');
setTimeout(() => {
if (row.parentNode === tbody) {
tbody.removeChild(row);
@@ -901,6 +1017,14 @@ function smoothRenderTable(tableId, newData, rowRenderer) {
newRows.forEach(row => {
row.style.opacity = '1';
});
// 移除过渡类和动画类
setTimeout(() => {
tbody.querySelectorAll('.table-row-fade-in').forEach(row => {
row.classList.remove('table-row-fade-in');
});
tbody.classList.remove('table-transition');
}, 300);
}, 10);
// 初始化表格排序
@@ -913,34 +1037,69 @@ function smoothRenderTable(tableId, newData, rowRenderer) {
// 平滑更新数字
function smoothUpdateNumber(element, oldValue, newValue) {
// 如果值相同,不更新
if (oldValue === newValue) return;
if (oldValue === newValue || !element) return;
// 根据数值差动态调整持续时间
const valueDiff = Math.abs(newValue - oldValue);
const baseDuration = 400;
const maxDuration = 1000;
// 数值变化越大,动画时间越长,但不超过最大值
const duration = Math.min(baseDuration + Math.log10(valueDiff + 1) * 200, maxDuration);
// 设置动画持续时间
const duration = 500;
const startTime = performance.now();
function animate(currentTime) {
const elapsedTime = currentTime - startTime;
const progress = Math.min(elapsedTime / duration, 1);
// 使用缓动函数
const easeOutQuad = 1 - (1 - progress) * (1 - progress);
// 使用easeOutQuart缓动函数使动画更自然
let easeOutProgress;
if (progress < 1) {
// 四阶缓动函数easeOutQuart
easeOutProgress = 1 - Math.pow(1 - progress, 4);
} else {
easeOutProgress = 1;
}
// 计算当前值
const currentValue = Math.floor(oldValue + (newValue - oldValue) * easeOutQuad);
// 根据不同的数值范围使用不同的插值策略
let currentValue;
if (valueDiff < 10) {
// 小数值变化,使用线性插值
currentValue = Math.floor(oldValue + (newValue - oldValue) * easeOutProgress);
} else if (valueDiff < 100) {
// 中等数值变化,使用四舍五入
currentValue = Math.round(oldValue + (newValue - oldValue) * easeOutProgress);
} else {
// 大数值变化,使用更平滑的插值
currentValue = Math.floor(oldValue + (newValue - oldValue) * easeOutProgress);
}
// 更新显示
element.textContent = formatNumber(currentValue);
// 添加微小的缩放动画效果
const scaleFactor = 1 + 0.05 * Math.sin(progress * Math.PI);
element.style.transform = `scale(${scaleFactor})`;
// 继续动画
if (progress < 1) {
requestAnimationFrame(animate);
} else {
// 动画完成
element.textContent = formatNumber(newValue);
// 重置缩放
element.style.transform = 'scale(1)';
// 触发最终的高亮效果
element.classList.add('number-update-complete');
setTimeout(() => {
element.classList.remove('number-update-complete');
}, 300);
}
}
// 重置元素样式
element.style.transform = 'scale(1)';
// 开始动画
requestAnimationFrame(animate);
}