增加暗色模式切换
This commit is contained in:
@@ -600,33 +600,79 @@ function updateStatsCards(stats) {
|
||||
// 存储正在进行的动画状态,避免动画重叠
|
||||
const animationInProgress = {};
|
||||
|
||||
// 解析格式化的数字字符串(如"1.2K")为实际数值
|
||||
function parseFormattedNumber(str) {
|
||||
if (!str || typeof str !== 'string') return 0;
|
||||
|
||||
// 移除逗号
|
||||
str = str.replace(/,/g, '');
|
||||
|
||||
// 检查是否是数字字符串
|
||||
const num = parseFloat(str);
|
||||
if (!isNaN(num)) {
|
||||
// 检查是否有K或M后缀
|
||||
if (str.includes('K') || str.includes('k')) {
|
||||
return num * 1000;
|
||||
} else if (str.includes('M') || str.includes('m')) {
|
||||
return num * 1000000;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 为数字元素添加翻页滚动特效
|
||||
function animateValue(elementId, newValue) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
const formattedNewValue = formatNumber(newValue);
|
||||
const currentValue = element.textContent;
|
||||
// 解析当前值和目标值
|
||||
const currentText = element.textContent;
|
||||
const currentValue = parseFormattedNumber(currentText);
|
||||
const targetValue = parseFloat(newValue) || 0;
|
||||
|
||||
// 如果值没有变化,不执行任何操作
|
||||
if (currentValue === formattedNewValue) {
|
||||
if (currentValue === targetValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 简化动画:使用CSS opacity过渡实现平滑更新
|
||||
element.style.transition = 'opacity 0.3s ease-in-out';
|
||||
element.style.opacity = '0';
|
||||
// 检查是否有正在进行的动画
|
||||
if (animationInProgress[elementId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用requestAnimationFrame确保平滑过渡
|
||||
requestAnimationFrame(() => {
|
||||
element.textContent = formattedNewValue;
|
||||
element.style.opacity = '1';
|
||||
// 标记动画正在进行
|
||||
animationInProgress[elementId] = true;
|
||||
|
||||
// 动画配置
|
||||
const duration = Math.min(800, Math.abs(targetValue - currentValue) * 2); // 根据数值变化大小调整动画持续时间
|
||||
const startTime = performance.now();
|
||||
const startValue = currentValue;
|
||||
|
||||
// 翻页滚动动画函数
|
||||
function animate(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// 移除transition样式,避免影响后续更新
|
||||
setTimeout(() => {
|
||||
element.style.transition = '';
|
||||
}, 300);
|
||||
});
|
||||
// 线性插值计算当前值
|
||||
const currentAnimatedValue = startValue + (targetValue - startValue) * progress;
|
||||
|
||||
// 更新显示,使用Math.round确保整数显示
|
||||
element.textContent = formatNumber(Math.round(currentAnimatedValue));
|
||||
|
||||
// 继续动画或结束
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
} else {
|
||||
// 确保最终值正确
|
||||
element.textContent = formatNumber(targetValue);
|
||||
// 标记动画完成
|
||||
delete animationInProgress[elementId];
|
||||
}
|
||||
}
|
||||
|
||||
// 开始动画
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// 更新百分比元素的函数
|
||||
@@ -726,17 +772,10 @@ function updateStatsCards(stats) {
|
||||
dnssecStatusElement.className = `text-sm flex items-center ${dnssecEnabled ? 'text-success' : 'text-danger'}`;
|
||||
}
|
||||
|
||||
if (dnssecSuccessElement) {
|
||||
dnssecSuccessElement.textContent = formatNumber(dnssecSuccess);
|
||||
}
|
||||
|
||||
if (dnssecFailedElement) {
|
||||
dnssecFailedElement.textContent = formatNumber(dnssecFailed);
|
||||
}
|
||||
|
||||
if (dnssecQueriesElement) {
|
||||
dnssecQueriesElement.textContent = formatNumber(dnssecQueries);
|
||||
}
|
||||
// 使用翻页滚动特效更新DNSSEC相关统计卡片
|
||||
animateValue('dnssec-success', dnssecSuccess);
|
||||
animateValue('dnssec-failed', dnssecFailed);
|
||||
animateValue('dnssec-queries', dnssecQueries);
|
||||
|
||||
// 直接更新文本和百分比,移除动画效果
|
||||
const topQueryTypeElement = document.getElementById('top-query-type');
|
||||
@@ -1535,6 +1574,11 @@ function initTimeRangeToggle() {
|
||||
|
||||
// 初始化图表
|
||||
function initCharts() {
|
||||
// 检测当前是否处于深色模式
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
// 根据主题模式设置不同的图例文字颜色
|
||||
const legendTextColor = isDarkMode ? '#e2e8f0' : '#4B5563';
|
||||
|
||||
// 初始化比例图表
|
||||
const ratioChartElement = document.getElementById('ratio-chart');
|
||||
if (!ratioChartElement) {
|
||||
@@ -1549,10 +1593,9 @@ function initCharts() {
|
||||
datasets: [{
|
||||
data: [0, 0, 0],
|
||||
backgroundColor: ['#34D399', '#EF4444', '#F59E0B'], // 优化的现代化配色
|
||||
borderWidth: 3, // 增加边框宽度,增强区块分隔
|
||||
borderColor: '#FFFFFF', // 白色边框,使各个扇区更清晰
|
||||
borderWidth: 0, // 移除边框宽度
|
||||
hoverOffset: 15, // 增加悬停偏移效果,增强交互体验
|
||||
hoverBorderWidth: 4, // 悬停时增加边框宽度
|
||||
hoverBorderWidth: 0, // 移除悬停时的边框宽度
|
||||
hoverBackgroundColor: ['#10B981', '#DC2626', '#D97706'], // 悬停时的深色效果
|
||||
borderRadius: 10, // 添加圆角效果,增强现代感
|
||||
borderSkipped: false // 显示所有边框
|
||||
@@ -1583,26 +1626,7 @@ function initCharts() {
|
||||
lineHeight: 1.5, // 调整行高
|
||||
usePointStyle: true, // 使用点样式代替方形图例,节省空间
|
||||
pointStyle: 'circle', // 使用圆形点样式
|
||||
color: '#4B5563', // 图例文本颜色
|
||||
generateLabels: function(chart) {
|
||||
// 自定义图例生成,添加更多样式控制
|
||||
const data = chart.data;
|
||||
if (data.labels.length && data.datasets.length) {
|
||||
return data.labels.map((label, i) => {
|
||||
const dataset = data.datasets[0];
|
||||
return {
|
||||
text: label,
|
||||
fillStyle: dataset.backgroundColor[i],
|
||||
strokeStyle: dataset.borderColor,
|
||||
lineWidth: dataset.borderWidth,
|
||||
pointStyle: 'circle',
|
||||
hidden: !chart.isDatasetVisible(0),
|
||||
index: i
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}
|
||||
color: legendTextColor // 根据主题设置图例文本颜色
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
@@ -1641,7 +1665,7 @@ function initCharts() {
|
||||
display: false // 不显示标题,由HTML标题代替
|
||||
}
|
||||
},
|
||||
cutout: '70%', // 调整中心空白区域比例,增强现代感
|
||||
cutout: '50%', // 调整中心空白区域比例,使环更粗
|
||||
// 增强元素配置
|
||||
elements: {
|
||||
arc: {
|
||||
@@ -1687,10 +1711,9 @@ function initCharts() {
|
||||
datasets: [{
|
||||
data: [1],
|
||||
backgroundColor: [queryTypeColors[0]],
|
||||
borderWidth: 3, // 增加边框宽度,增强区块分隔
|
||||
borderColor: '#fff', // 白色边框,使各个扇区更清晰
|
||||
borderWidth: 0, // 移除边框宽度
|
||||
hoverOffset: 15, // 增加悬停偏移效果,增强交互体验
|
||||
hoverBorderWidth: 4, // 悬停时增加边框宽度
|
||||
hoverBorderWidth: 0, // 移除悬停时的边框宽度
|
||||
hoverBackgroundColor: queryTypeColors.map(color => {
|
||||
// 生成悬停时的深色效果
|
||||
const hex = color.replace('#', '');
|
||||
@@ -1728,26 +1751,7 @@ function initCharts() {
|
||||
lineHeight: 1.5, // 调整行高
|
||||
usePointStyle: true, // 使用点样式代替方形图例,节省空间
|
||||
pointStyle: 'circle', // 使用圆形点样式
|
||||
color: '#4B5563', // 图例文本颜色
|
||||
generateLabels: function(chart) {
|
||||
// 自定义图例生成,添加更多样式控制
|
||||
const data = chart.data;
|
||||
if (data.labels.length && data.datasets.length) {
|
||||
return data.labels.map((label, i) => {
|
||||
const dataset = data.datasets[0];
|
||||
return {
|
||||
text: label,
|
||||
fillStyle: dataset.backgroundColor[i],
|
||||
strokeStyle: dataset.borderColor,
|
||||
lineWidth: dataset.borderWidth,
|
||||
pointStyle: 'circle',
|
||||
hidden: !chart.isDatasetVisible(0),
|
||||
index: i
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
},
|
||||
color: legendTextColor, // 根据主题设置图例文本颜色
|
||||
// 启用图例点击交互
|
||||
onClick: function(event, legendItem, legend) {
|
||||
// 切换对应数据的显示
|
||||
@@ -1757,7 +1761,7 @@ function initCharts() {
|
||||
ci.update();
|
||||
},
|
||||
// 图例悬停样式
|
||||
fontColor: '#4B5563',
|
||||
fontColor: legendTextColor,
|
||||
usePointStyle: true,
|
||||
pointStyle: 'circle'
|
||||
}
|
||||
@@ -1798,7 +1802,7 @@ function initCharts() {
|
||||
display: false // 不显示标题,由HTML标题代替
|
||||
}
|
||||
},
|
||||
cutout: '70%', // 调整中心空白区域比例,增强现代感
|
||||
cutout: '50%', // 调整中心空白区域比例,使环更粗
|
||||
// 增强元素配置
|
||||
elements: {
|
||||
arc: {
|
||||
@@ -3251,3 +3255,100 @@ async function loadDashboardData() {
|
||||
// 静默失败,不显示通知以免打扰用户
|
||||
}
|
||||
}
|
||||
|
||||
// 主题切换功能
|
||||
function initThemeToggle() {
|
||||
const themeToggleBtn = document.getElementById('theme-toggle');
|
||||
if (!themeToggleBtn) return;
|
||||
|
||||
// 初始化主题
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
updateThemeIcon(true);
|
||||
}
|
||||
|
||||
// 添加主题切换事件监听器
|
||||
themeToggleBtn.addEventListener('click', toggleTheme);
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
function toggleTheme() {
|
||||
const isDark = document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('theme', isDark ? 'dark' : 'light');
|
||||
updateThemeIcon(isDark);
|
||||
|
||||
// 更新图表图例文字颜色
|
||||
updateChartLegendColors(isDark);
|
||||
}
|
||||
|
||||
// 更新图表图例文字颜色
|
||||
function updateChartLegendColors(isDark) {
|
||||
const legendTextColor = isDark ? '#e2e8f0' : '#4B5563';
|
||||
|
||||
// 更新比例图表图例文字颜色
|
||||
if (ratioChart) {
|
||||
ratioChart.options.plugins.legend.labels.color = legendTextColor;
|
||||
ratioChart.options.plugins.legend.labels.fontColor = legendTextColor;
|
||||
// 平滑更新图表
|
||||
ratioChart.update({
|
||||
duration: 300,
|
||||
easing: 'easeInOutQuart'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新解析类型统计饼图图例文字颜色
|
||||
if (queryTypeChart) {
|
||||
queryTypeChart.options.plugins.legend.labels.color = legendTextColor;
|
||||
queryTypeChart.options.plugins.legend.labels.fontColor = legendTextColor;
|
||||
// 平滑更新图表
|
||||
queryTypeChart.update({
|
||||
duration: 300,
|
||||
easing: 'easeInOutQuart'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新DNS请求统计图表图例文字颜色
|
||||
if (dnsRequestsChart) {
|
||||
if (dnsRequestsChart.options.plugins.legend) {
|
||||
dnsRequestsChart.options.plugins.legend.labels.color = legendTextColor;
|
||||
dnsRequestsChart.options.plugins.legend.labels.fontColor = legendTextColor;
|
||||
// 平滑更新图表
|
||||
dnsRequestsChart.update({
|
||||
duration: 300,
|
||||
easing: 'easeInOutQuart'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新详细DNS请求统计图表图例文字颜色
|
||||
if (detailedDnsRequestsChart) {
|
||||
if (detailedDnsRequestsChart.options.plugins.legend) {
|
||||
detailedDnsRequestsChart.options.plugins.legend.labels.color = legendTextColor;
|
||||
detailedDnsRequestsChart.options.plugins.legend.labels.fontColor = legendTextColor;
|
||||
// 平滑更新图表
|
||||
detailedDnsRequestsChart.update({
|
||||
duration: 300,
|
||||
easing: 'easeInOutQuart'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新主题图标
|
||||
function updateThemeIcon(isDark) {
|
||||
const themeToggleBtn = document.getElementById('theme-toggle');
|
||||
if (!themeToggleBtn) return;
|
||||
|
||||
const icon = themeToggleBtn.querySelector('i');
|
||||
if (isDark) {
|
||||
icon.classList.remove('fa-moon-o');
|
||||
icon.classList.add('fa-sun-o');
|
||||
} else {
|
||||
icon.classList.remove('fa-sun-o');
|
||||
icon.classList.add('fa-moon-o');
|
||||
}
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化主题切换
|
||||
window.addEventListener('DOMContentLoaded', initThemeToggle);
|
||||
|
||||
Reference in New Issue
Block a user