web异常待修复:屏蔽统计
This commit is contained in:
@@ -64,6 +64,57 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 优化的表格过渡动画 */
|
||||
.table-transition {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 表格行淡入动画 */
|
||||
.table-row-fade-in {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
/* 表格行淡出动画 */
|
||||
.table-row-fade-out {
|
||||
animation: fadeOut 0.3s ease-in;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 表格行高亮效果 */
|
||||
.table-row-highlight {
|
||||
background-color: rgba(75, 192, 192, 0.2) !important;
|
||||
transition: background-color 0.3s ease;
|
||||
animation: highlightPulse 1s ease;
|
||||
}
|
||||
|
||||
@keyframes highlightPulse {
|
||||
0% {
|
||||
background-color: rgba(75, 192, 192, 0.2);
|
||||
}
|
||||
50% {
|
||||
background-color: rgba(75, 192, 192, 0.4);
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(75, 192, 192, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 数字更新完成动画 */
|
||||
.number-update-complete {
|
||||
animation: numberComplete 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes numberComplete {
|
||||
0% {
|
||||
color: #2196F3;
|
||||
font-weight: bold;
|
||||
}
|
||||
100% {
|
||||
color: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user