web异常待修复

This commit is contained in:
Alex Yang
2025-11-24 12:54:25 +08:00
parent 23b8847c35
commit 86faca036d
5 changed files with 2201 additions and 120 deletions

View File

@@ -0,0 +1,5 @@
{
"blockedDomainsCount": {},
"resolvedDomainsCount": {},
"lastSaved": "2025-11-24T12:53:57.372276148+08:00"
}

View File

@@ -1,10 +1,10 @@
{
"stats": {
"Queries": 10160,
"Blocked": 984,
"Allowed": 9642,
"Errors": 19,
"LastQuery": "2025-11-24T10:58:52.576912236+08:00"
"Queries": 10356,
"Blocked": 1160,
"Allowed": 9740,
"Errors": 29,
"LastQuery": "2025-11-24T11:14:45.256891441+08:00"
},
"blockedDomains": {
"acd.op.hicloud.com": {
@@ -27,16 +27,31 @@
"Count": 6,
"LastSeen": "2025-11-23T19:06:10.691565251+08:00"
},
"adjus.com": {
"Domain": "adjus.com",
"Count": 2,
"LastSeen": "2025-11-24T11:11:17.334769536+08:00"
},
"adjust.com": {
"Domain": "adjust.com",
"Count": 64,
"LastSeen": "2025-11-24T11:10:54.322973064+08:00"
},
"adjust.com3": {
"Domain": "adjust.com3",
"Count": 2,
"LastSeen": "2025-11-24T10:52:50.65657129+08:00"
"LastSeen": "2025-11-24T11:11:02.160342476+08:00"
},
"adjust.net": {
"Domain": "adjust.net",
"Count": 8,
"LastSeen": "2025-11-24T01:47:49.401607336+08:00"
},
"adjust1.cs": {
"Domain": "adjust1.cs",
"Count": 2,
"LastSeen": "2025-11-24T11:11:12.680852315+08:00"
},
"agpicnsp-drcn.dbankcdn.com": {
"Domain": "agpicnsp-drcn.dbankcdn.com",
"Count": 6,
@@ -44,8 +59,8 @@
},
"api-drcn.theme.dbankcloud.cn": {
"Domain": "api-drcn.theme.dbankcloud.cn",
"Count": 2,
"LastSeen": "2025-11-24T09:38:52.070607203+08:00"
"Count": 4,
"LastSeen": "2025-11-24T11:09:56.504837268+08:00"
},
"api.weathercn.com": {
"Domain": "api.weathercn.com",
@@ -124,8 +139,8 @@
},
"datacollabo-drcn.platform.dbankcloud.cn": {
"Domain": "datacollabo-drcn.platform.dbankcloud.cn",
"Count": 2,
"LastSeen": "2025-11-24T03:13:34.528874084+08:00"
"Count": 4,
"LastSeen": "2025-11-24T11:14:45.02112075+08:00"
},
"datasync-drcn.cloud.dbankcloud.cn": {
"Domain": "datasync-drcn.cloud.dbankcloud.cn",
@@ -134,8 +149,8 @@
},
"dnkeeper.platform.dbankcloud.cn": {
"Domain": "dnkeeper.platform.dbankcloud.cn",
"Count": 42,
"LastSeen": "2025-11-24T10:14:41.401054875+08:00"
"Count": 46,
"LastSeen": "2025-11-24T11:14:43.701601859+08:00"
},
"event-drcn.push.dbankcloud.cn": {
"Domain": "event-drcn.push.dbankcloud.cn",
@@ -149,8 +164,8 @@
},
"events.op.hicloud.com": {
"Domain": "events.op.hicloud.com",
"Count": 10,
"LastSeen": "2025-11-24T09:38:58.251391494+08:00"
"Count": 12,
"LastSeen": "2025-11-24T11:09:58.304001135+08:00"
},
"f00b1b869deb32d2ad60ba514bb876ea.b.hon.cc.cdnhwc8.com": {
"Domain": "f00b1b869deb32d2ad60ba514bb876ea.b.hon.cc.cdnhwc8.com",
@@ -159,8 +174,8 @@
},
"h5hosting.dbankcdn.com": {
"Domain": "h5hosting.dbankcdn.com",
"Count": 2,
"LastSeen": "2025-11-24T03:13:34.79141854+08:00"
"Count": 4,
"LastSeen": "2025-11-24T11:14:45.257305828+08:00"
},
"hiboard-drcn.ai.dbankcloud.cn": {
"Domain": "hiboard-drcn.ai.dbankcloud.cn",
@@ -184,8 +199,8 @@
},
"hwid-drcn.platform.hicloud.com": {
"Domain": "hwid-drcn.platform.hicloud.com",
"Count": 10,
"LastSeen": "2025-11-24T10:54:44.90022211+08:00"
"Count": 12,
"LastSeen": "2025-11-24T11:14:43.701391787+08:00"
},
"hwid.platform.hicloud.com": {
"Domain": "hwid.platform.hicloud.com",
@@ -204,8 +219,8 @@
},
"magazine-drcn.theme.dbankcloud.cn": {
"Domain": "magazine-drcn.theme.dbankcloud.cn",
"Count": 10,
"LastSeen": "2025-11-24T09:38:52.08277503+08:00"
"Count": 12,
"LastSeen": "2025-11-24T11:09:56.50440237+08:00"
},
"metrics1-drcn.dt.dbankcloud.cn": {
"Domain": "metrics1-drcn.dt.dbankcloud.cn",
@@ -224,8 +239,8 @@
},
"rcm-cus-drcn.platform.dbankcloud.cn": {
"Domain": "rcm-cus-drcn.platform.dbankcloud.cn",
"Count": 2,
"LastSeen": "2025-11-24T03:13:34.528406169+08:00"
"Count": 4,
"LastSeen": "2025-11-24T11:14:45.020111224+08:00"
},
"sdkserver-drcn.op.dbankcloud.cn": {
"Domain": "sdkserver-drcn.op.dbankcloud.cn",
@@ -274,8 +289,8 @@
},
"tsms-drcn.security.dbankcloud.cn": {
"Domain": "tsms-drcn.security.dbankcloud.cn",
"Count": 2,
"LastSeen": "2025-11-24T03:13:34.528102875+08:00"
"Count": 4,
"LastSeen": "2025-11-24T11:14:45.019603773+08:00"
},
"userk-drcn.cloud.dbankcloud.cn": {
"Domain": "userk-drcn.cloud.dbankcloud.cn",
@@ -296,8 +311,8 @@
},
"abt-drcn.platform.dbankcloud.com": {
"Domain": "abt-drcn.platform.dbankcloud.com",
"Count": 4,
"LastSeen": "2025-11-24T08:06:49.435747722+08:00"
"Count": 5,
"LastSeen": "2025-11-24T11:14:45.021921328+08:00"
},
"acd.op.hicloud.com": {
"Domain": "acd.op.hicloud.com",
@@ -314,11 +329,41 @@
"Count": 16,
"LastSeen": "2025-11-24T00:32:55.878106815+08:00"
},
"adjus.com.amazehome.xyz": {
"Domain": "adjus.com.amazehome.xyz",
"Count": 2,
"LastSeen": "2025-11-24T11:11:17.263847943+08:00"
},
"adjust.amazehome.xyz": {
"Domain": "adjust.amazehome.xyz",
"Count": 2,
"LastSeen": "2025-11-24T11:11:07.080717636+08:00"
},
"adjust.com.amazehome.xyz": {
"Domain": "adjust.com.amazehome.xyz",
"Count": 56,
"LastSeen": "2025-11-24T11:10:54.256320033+08:00"
},
"adjust.com3.amazehome.xyz": {
"Domain": "adjust.com3.amazehome.xyz",
"Count": 2,
"LastSeen": "2025-11-24T11:11:02.068019996+08:00"
},
"adjust.net.amazehome.xyz": {
"Domain": "adjust.net.amazehome.xyz",
"Count": 8,
"LastSeen": "2025-11-24T01:47:49.399009577+08:00"
},
"adjust1.amazehome.xyz": {
"Domain": "adjust1.amazehome.xyz",
"Count": 2,
"LastSeen": "2025-11-24T11:11:08.456696388+08:00"
},
"adjust1.cs.amazehome.xyz": {
"Domain": "adjust1.cs.amazehome.xyz",
"Count": 1,
"LastSeen": "2025-11-24T11:11:12.61355646+08:00"
},
"aeventlog.beacon.qq.com": {
"Domain": "aeventlog.beacon.qq.com",
"Count": 24,
@@ -341,8 +386,8 @@
},
"apd-pcdnwxstat.teg.tencent-cloud.net": {
"Domain": "apd-pcdnwxstat.teg.tencent-cloud.net",
"Count": 33,
"LastSeen": "2025-11-24T10:36:53.271030952+08:00"
"Count": 35,
"LastSeen": "2025-11-24T11:08:27.539325599+08:00"
},
"api-drcn.theme.dbankcloud.cn": {
"Domain": "api-drcn.theme.dbankcloud.cn",
@@ -471,8 +516,8 @@
},
"contentcenter-drcn.dbankcdn.cn": {
"Domain": "contentcenter-drcn.dbankcdn.cn",
"Count": 14,
"LastSeen": "2025-11-24T10:01:25.221069297+08:00"
"Count": 16,
"LastSeen": "2025-11-24T11:14:45.021702297+08:00"
},
"contentcenter-drcn.dbankcdn.com": {
"Domain": "contentcenter-drcn.dbankcdn.com",
@@ -646,8 +691,8 @@
},
"nearby-find-api-drcn.hms.dbankcloud.com": {
"Domain": "nearby-find-api-drcn.hms.dbankcloud.com",
"Count": 2,
"LastSeen": "2025-11-24T10:14:41.380422058+08:00"
"Count": 3,
"LastSeen": "2025-11-24T11:14:43.679722257+08:00"
},
"nsp-hicloud-cloudbackupnorth9-p06-drcn.obs.dualstack.cn-north-9.myhuaweicloud.cn": {
"Domain": "nsp-hicloud-cloudbackupnorth9-p06-drcn.obs.dualstack.cn-north-9.myhuaweicloud.cn",
@@ -661,13 +706,13 @@
},
"oauth-login-drcn.platform.dbankcloud.com": {
"Domain": "oauth-login-drcn.platform.dbankcloud.com",
"Count": 2,
"LastSeen": "2025-11-24T10:14:41.381115773+08:00"
"Count": 3,
"LastSeen": "2025-11-24T11:14:43.683804604+08:00"
},
"openlocation-drcn.platform.dbankcloud.com": {
"Domain": "openlocation-drcn.platform.dbankcloud.com",
"Count": 2,
"LastSeen": "2025-11-24T10:14:41.381175213+08:00"
"Count": 3,
"LastSeen": "2025-11-24T11:14:43.683651239+08:00"
},
"paydns.wechatpay.cn": {
"Domain": "paydns.wechatpay.cn",
@@ -2661,13 +2706,13 @@
},
"so.com": {
"Domain": "so.com",
"Count": 15,
"LastSeen": "2025-11-24T10:54:53.278047903+08:00"
"Count": 29,
"LastSeen": "2025-11-24T11:09:22.651757829+08:00"
},
"so.com.amazehome.xyz": {
"Domain": "so.com.amazehome.xyz",
"Count": 5,
"LastSeen": "2025-11-24T10:54:53.031986202+08:00"
"Count": 16,
"LastSeen": "2025-11-24T11:09:22.522520185+08:00"
},
"so.qss-lb.com": {
"Domain": "so.qss-lb.com",
@@ -2744,7 +2789,8 @@
"2025-11-24-07": 40,
"2025-11-24-08": 63,
"2025-11-24-09": 20,
"2025-11-24-10": 132
"2025-11-24-10": 132,
"2025-11-24-11": 88
},
"lastSaved": "2025-11-24T11:00:47.366358059+08:00"
"lastSaved": "2025-11-24T11:18:29.299605227+08:00"
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,72 @@
width: 100% !important;
height: 100% !important;
}
/* 统计数据更新动画 */
.stat-value.update {
position: relative;
animation: stat-pulse 1s ease;
}
@keyframes stat-pulse {
0% {
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(76, 175, 80, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);
}
}
/* 表格行淡入淡出动画 */
table tr.fade-in {
animation: fadeIn 0.3s ease-out;
}
table tr.fade-out {
animation: fadeOut 0.3s ease-in;
opacity: 0;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeOut {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-10px);
}
}
/* 通知样式 */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 5px;
color: white;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 10000;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
}
</style>
</head>
<body>

View File

@@ -1,5 +1,9 @@
// 初始化仪表盘面板
function initDashboardPanel() {
// 初始化小型图表
if (typeof initMiniCharts === 'function') {
initMiniCharts();
}
// 加载统计数据
loadDashboardData();
// 启动实时更新
@@ -29,19 +33,19 @@ function updateStatCards() {
if (data && data.dns) {
// 屏蔽请求
const blockedCount = data.dns.Blocked || data.dns.blocked || 0;
updateStatCard('blocked-count', blockedCount);
smoothUpdateStatCard('blocked-count', blockedCount);
// 允许请求
const allowedCount = data.dns.Allowed || data.dns.allowed || 0;
updateStatCard('allowed-count', allowedCount);
smoothUpdateStatCard('allowed-count', allowedCount);
// 错误请求
const errorCount = data.dns.Errors || data.dns.errors || 0;
updateStatCard('error-count', errorCount);
smoothUpdateStatCard('error-count', errorCount);
// 总请求数
const totalCount = blockedCount + allowedCount + errorCount;
updateStatCard('total-queries', totalCount);
smoothUpdateStatCard('total-queries', totalCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
@@ -56,15 +60,14 @@ function updateStatCards() {
}
} else {
// 处理其他可能的数据格式
// 修复语法错误,使用传统的对象属性访问方式
const blockedValue = data && (data.Blocked !== undefined ? data.Blocked : (data.blocked !== undefined ? data.blocked : 0));
const allowedValue = data && (data.Allowed !== undefined ? data.Allowed : (data.allowed !== undefined ? data.allowed : 0));
const errorValue = data && (data.Errors !== undefined ? data.Errors : (data.errors !== undefined ? data.errors : 0));
updateStatCard('blocked-count', blockedValue);
updateStatCard('allowed-count', allowedValue);
updateStatCard('error-count', errorValue);
smoothUpdateStatCard('blocked-count', blockedValue);
smoothUpdateStatCard('allowed-count', allowedValue);
smoothUpdateStatCard('error-count', errorValue);
const totalCount = blockedValue + allowedValue + errorValue;
updateStatCard('total-queries', totalCount);
smoothUpdateStatCard('total-queries', totalCount);
}
})
.catch(error => {
@@ -75,13 +78,32 @@ function updateStatCards() {
apiRequest('/shield')
.then(data => {
let rulesCount = 0;
// 增强的数据格式处理,确保能正确处理各种返回格式
if (Array.isArray(data)) {
rulesCount = data.length;
} else if (data && data.rules && Array.isArray(data.rules)) {
rulesCount = data.rules.length;
} else if (data && data.domainRules) {
// 处理可能的规则分类格式
let domainRulesCount = 0;
let regexRulesCount = 0;
if (Array.isArray(data.domainRules)) {
domainRulesCount = data.domainRules.length;
} else if (typeof data.domainRules === 'object') {
domainRulesCount = Object.keys(data.domainRules).length;
}
updateStatCard('rules-count', rulesCount);
if (data.regexRules && Array.isArray(data.regexRules)) {
regexRulesCount = data.regexRules.length;
}
rulesCount = domainRulesCount + regexRulesCount;
}
// 确保至少显示0而不是--
smoothUpdateStatCard('rules-count', rulesCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
@@ -94,6 +116,41 @@ function updateStatCards() {
})
.catch(error => {
console.error('获取规则数失败:', error);
// 即使出错也要设置为0避免显示--
smoothUpdateStatCard('rules-count', 0);
});
// 获取Hosts条目数量
apiRequest('/shield/hosts')
.then(data => {
let hostsCount = 0;
// 处理各种可能的数据格式
if (Array.isArray(data)) {
hostsCount = data.length;
} else if (data && data.hosts && Array.isArray(data.hosts)) {
hostsCount = data.hosts.length;
} else if (data && typeof data === 'object' && data !== null) {
// 如果是对象格式,计算键的数量
hostsCount = Object.keys(data).length;
}
// 确保至少显示0而不是--
smoothUpdateStatCard('hosts-count', hostsCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
updateDataHistory('hosts', hostsCount);
}
if (typeof updateMiniChart === 'function' && typeof dataHistory !== 'undefined') {
updateMiniChart('hosts-chart', dataHistory.hosts);
}
})
.catch(error => {
console.error('获取Hosts数量失败:', error);
// 即使出错也要设置为0避免显示--
smoothUpdateStatCard('hosts-count', 0);
});
// 获取Hosts条目数
@@ -106,7 +163,7 @@ function updateStatCards() {
hostsCount = data.hosts.length;
}
updateStatCard('hosts-count', hostsCount);
smoothUpdateStatCard('hosts-count', hostsCount);
// 更新数据历史记录和小型图表
if (typeof updateDataHistory === 'function') {
@@ -122,6 +179,7 @@ function updateStatCards() {
});
}
// 更新单个统计卡片
function updateStatCard(elementId, value) {
const element = document.getElementById(elementId);
@@ -139,6 +197,61 @@ function updateStatCard(elementId, value) {
}
}
// 平滑更新统计卡片(数字递增动画)
function smoothUpdateStatCard(elementId, newValue) {
const element = document.getElementById(elementId);
if (!element) return;
// 获取旧值
const oldValue = previousStats[elementId] || 0;
// 如果值相同,不更新
if (newValue === oldValue) return;
// 如果是初始值,直接更新
if (oldValue === 0 || oldValue === '--') {
updateStatCard(elementId, newValue);
return;
}
// 设置动画持续时间
const duration = 500; // 500ms
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);
// 计算当前值
const currentValue = Math.floor(oldValue + (newValue - oldValue) * easeOutQuad);
// 更新显示
element.textContent = formatNumber(currentValue);
// 继续动画
if (progress < 1) {
requestAnimationFrame(animate);
} else {
// 动画完成,设置最终值
element.textContent = formatNumber(newValue);
// 添加光晕效果
element.classList.add('update');
setTimeout(() => {
element.classList.remove('update');
}, 1000);
// 更新记录
previousStats[elementId] = newValue;
}
}
// 开始动画
requestAnimationFrame(animate);
}
// 加载24小时统计数据
function loadHourlyStats() {
apiRequest('/hourly-stats')
@@ -524,7 +637,6 @@ function renderRequestsPieChart(labels, data) {
// 加载最常屏蔽的域名
function loadTopBlockedDomains() {
// 首先获取表格元素并显示加载状态
// 修复语法错误使用传统的DOM访问方式
const topBlockedTable = document.getElementById('top-blocked-table');
const tbody = topBlockedTable ? topBlockedTable.querySelector('tbody') : null;
if (tbody) {
@@ -537,12 +649,9 @@ function loadTopBlockedDomains() {
// 处理多种可能的数据格式,特别优化对用户提供格式的支持
let processedData = [];
console.log('最常屏蔽域名API返回数据:', data);
if (Array.isArray(data)) {
// 数组格式:直接使用,并过滤出有效的域名数据
processedData = data.filter(item => item && (item.domain || item.name || item.Domain || item.Name) && (item.count !== undefined || item.Count !== undefined || item.hits !== undefined || item.Hits !== undefined));
console.log('处理后的域名数据:', processedData);
} else if (data && data.domains && Array.isArray(data.domains)) {
// 嵌套在domains属性中
processedData = data.domains;
@@ -554,7 +663,7 @@ function loadTopBlockedDomains() {
}));
}
renderTopBlockedDomains(processedData);
smoothRenderTable('#top-blocked-table', processedData, renderDomainRow);
})
.catch(error => {
console.error('获取最常屏蔽域名失败:', error);
@@ -570,48 +679,6 @@ function loadTopBlockedDomains() {
});
}
// 渲染最常屏蔽的域名表格
function renderTopBlockedDomains(domains) {
// 修复语法错误使用传统的DOM访问方式
const topBlockedTable = document.getElementById('top-blocked-table');
const tbody = topBlockedTable ? topBlockedTable.querySelector('tbody') : null;
if (!tbody) return;
console.log('准备渲染的域名数据:', domains);
if (!domains || domains.length === 0) {
showEmpty(tbody, '暂无屏蔽记录');
return;
}
tbody.innerHTML = '';
domains.forEach((domain, index) => {
if (!domain) return;
// 支持不同的字段名和格式,特别针对用户提供的数据格式优化
const domainName = domain.domain || domain.name || domain.Domain || domain.Name || '未知域名';
const count = domain.count !== undefined ? domain.count :
(domain.Count !== undefined ? domain.Count :
(domain.hits !== undefined ? domain.hits :
(domain.Hits !== undefined ? domain.Hits : 0)));
console.log(`渲染域名 ${index + 1}:`, {domainName, count});
const row = document.createElement('tr');
row.innerHTML = `
<td>${domainName}</td>
<td>${formatNumber(count)}</td>
`;
tbody.appendChild(row);
});
// 初始化表格排序
if (typeof initTableSort === 'function') {
initTableSort('top-blocked-table');
}
}
// 加载最常解析的域名
function loadTopResolvedDomains() {
apiRequest('/top-resolved')
@@ -633,7 +700,7 @@ function loadTopResolvedDomains() {
}));
}
renderTopResolvedDomains(processedData);
smoothRenderTable('#top-resolved-table', processedData, renderDomainRow);
})
.catch(error => {
console.error('获取最常解析域名失败:', error);
@@ -650,31 +717,177 @@ function loadTopResolvedDomains() {
});
}
// 渲染最常解析的域名表格
function renderTopResolvedDomains(domains) {
const tbody = document.getElementById('top-resolved-table').querySelector('tbody');
// 渲染域名行
function renderDomainRow(item, index) {
if (!item) return null;
// 支持不同的字段名和格式
const domainName = item.domain || item.name || item.Domain || item.Name || '未知域名';
const count = item.count !== undefined ? item.count :
(item.Count !== undefined ? item.Count :
(item.hits !== undefined ? item.hits :
(item.Hits !== undefined ? item.Hits : 0)));
const row = document.createElement('tr');
row.className = 'fade-in'; // 添加淡入动画类
row.dataset.domain = domainName;
row.dataset.count = count;
row.innerHTML = `
<td>${domainName}</td>
<td class="count-cell">${formatNumber(count)}</td>
`;
// 设置动画延迟,创建级联效果
row.style.animationDelay = `${index * 50}ms`;
return row;
}
// 平滑渲染表格数据
function smoothRenderTable(tableId, newData, rowRenderer) {
const table = document.getElementById(tableId);
const tbody = table ? table.querySelector('tbody') : null;
if (!tbody) return;
if (!domains || domains.length === 0) {
showEmpty(tbody, '暂无解析记录');
if (!newData || newData.length === 0) {
showEmpty(tbody, '暂无数据记录');
return;
}
tbody.innerHTML = '';
// 创建映射以提高查找效率
const oldRows = Array.from(tbody.querySelectorAll('tr'));
const rowMap = new Map();
domains.forEach((domain, index) => {
// 支持不同的字段名和格式
const domainName = domain.domain || domain.name || domain.Domain || domain.Name || '未知域名';
const count = domain.count || domain.Count || domain.hits || domain.Hits || 0;
const row = document.createElement('tr');
row.innerHTML = `
<td>${domainName}</td>
<td>${formatNumber(count)}</td>
`;
tbody.appendChild(row);
oldRows.forEach(row => {
if (!row.querySelector('td:first-child')) return;
const key = row.dataset.domain || row.querySelector('td:first-child').textContent;
rowMap.set(key, row);
});
// 准备新的数据行
const newRows = [];
const updatedRows = new Set();
// 处理每一条新数据
newData.forEach((item, index) => {
const key = item.domain || item.name || item.Domain || item.Name || '未知域名';
if (rowMap.has(key)) {
// 数据项已存在,更新它
const existingRow = rowMap.get(key);
const oldCount = parseInt(existingRow.dataset.count) || 0;
const count = item.count !== undefined ? item.count :
(item.Count !== undefined ? item.Count :
(item.hits !== undefined ? item.hits :
(item.Hits !== undefined ? item.Hits : 0)));
// 更新数据属性
existingRow.dataset.count = count;
// 如果计数变化,应用平滑更新
if (oldCount !== count) {
const countCell = existingRow.querySelector('.count-cell');
if (countCell) {
smoothUpdateNumber(countCell, oldCount, count);
}
}
// 更新位置
existingRow.style.animationDelay = `${index * 50}ms`;
newRows.push(existingRow);
updatedRows.add(key);
} else {
// 新数据项,创建新行
const newRow = rowRenderer(item, index);
if (newRow) {
// 先设置透明度为0避免在错误位置闪烁
newRow.style.opacity = '0';
newRows.push(newRow);
}
}
});
// 移除不再存在的数据行
oldRows.forEach(row => {
if (!row.querySelector('td:first-child')) return;
const key = row.dataset.domain || row.querySelector('td:first-child').textContent;
if (!updatedRows.has(key)) {
// 添加淡出动画
row.classList.add('fade-out');
setTimeout(() => {
if (row.parentNode === tbody) {
tbody.removeChild(row);
}
}, 300);
}
});
// 批量更新表格内容,减少重排
requestAnimationFrame(() => {
// 保留未移除的行并按新顺序插入
const fragment = document.createDocumentFragment();
newRows.forEach(row => {
// 如果是新行,添加到文档片段
if (!row.parentNode || row.parentNode !== tbody) {
fragment.appendChild(row);
}
// 如果是已有行,移除它以便按新顺序重新插入
else if (tbody.contains(row)) {
tbody.removeChild(row);
fragment.appendChild(row);
}
});
// 将文档片段添加到表格
tbody.appendChild(fragment);
// 触发动画
setTimeout(() => {
newRows.forEach(row => {
row.style.opacity = '1';
});
}, 10);
// 初始化表格排序
initTableSort('top-resolved-table');
if (typeof initTableSort === 'function') {
initTableSort(tableId);
}
});
}
// 平滑更新数字
function smoothUpdateNumber(element, oldValue, newValue) {
// 如果值相同,不更新
if (oldValue === newValue) return;
// 设置动画持续时间
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);
// 计算当前值
const currentValue = Math.floor(oldValue + (newValue - oldValue) * easeOutQuad);
// 更新显示
element.textContent = formatNumber(currentValue);
// 继续动画
if (progress < 1) {
requestAnimationFrame(animate);
} else {
// 动画完成
element.textContent = formatNumber(newValue);
}
}
// 开始动画
requestAnimationFrame(animate);
}