web请求趋势视图优化
This commit is contained in:
@@ -460,24 +460,150 @@ function updateRecentBlockedTable(domains) {
|
||||
|
||||
// 当前选中的时间范围
|
||||
let currentTimeRange = '24h'; // 默认为24小时
|
||||
let isMixedView = false; // 是否为混合视图
|
||||
let lastSelectedIndex = 0; // 最后选中的按钮索引
|
||||
|
||||
// 初始化时间范围切换
|
||||
function initTimeRangeToggle() {
|
||||
const timeRangeButtons = document.querySelectorAll('.time-range-btn');
|
||||
timeRangeButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
// 移除所有按钮的激活状态
|
||||
timeRangeButtons.forEach(btn => btn.classList.remove('active'));
|
||||
// 添加当前按钮的激活状态
|
||||
button.classList.add('active');
|
||||
// 更新当前时间范围
|
||||
currentTimeRange = button.dataset.range;
|
||||
console.log('初始化时间范围切换');
|
||||
// 查找所有可能的时间范围按钮类名
|
||||
const timeRangeButtons = document.querySelectorAll('.time-range-btn, .time-range-button, .timerange-btn, button[data-range]');
|
||||
console.log('找到时间范围按钮数量:', timeRangeButtons.length);
|
||||
|
||||
if (timeRangeButtons.length === 0) {
|
||||
console.warn('未找到时间范围按钮,请检查HTML中的类名');
|
||||
return;
|
||||
}
|
||||
|
||||
// 定义三个按钮的不同样式配置,增加activeHover属性
|
||||
const buttonStyles = [
|
||||
{ // 24小时按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-blue-100'],
|
||||
active: ['bg-blue-500', 'text-white'],
|
||||
activeHover: ['hover:bg-blue-400'] // 选中时的浅色悬停
|
||||
},
|
||||
{ // 7天按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-green-100'],
|
||||
active: ['bg-green-500', 'text-white'],
|
||||
activeHover: ['hover:bg-green-400'] // 选中时的浅色悬停
|
||||
},
|
||||
{ // 30天按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-purple-100'],
|
||||
active: ['bg-purple-500', 'text-white'],
|
||||
activeHover: ['hover:bg-purple-400'] // 选中时的浅色悬停
|
||||
},
|
||||
{ // 混合视图按钮
|
||||
normal: ['bg-gray-100', 'text-gray-700'],
|
||||
hover: ['hover:bg-gray-200'],
|
||||
active: ['bg-gray-500', 'text-white'],
|
||||
activeHover: ['hover:bg-gray-400'] // 选中时的浅色悬停
|
||||
}
|
||||
];
|
||||
|
||||
// 为所有按钮设置初始样式和事件
|
||||
timeRangeButtons.forEach((button, index) => {
|
||||
// 使用相应的样式配置
|
||||
const styleConfig = buttonStyles[index % buttonStyles.length];
|
||||
|
||||
// 移除所有按钮的初始样式
|
||||
button.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-gray-200', 'text-gray-700',
|
||||
'bg-green-500', 'bg-purple-500', 'bg-gray-100');
|
||||
|
||||
// 设置非选中状态样式
|
||||
button.classList.add('transition-colors', 'duration-200');
|
||||
button.classList.add(...styleConfig.normal);
|
||||
button.classList.add(...styleConfig.hover);
|
||||
|
||||
// 移除鼠标悬停提示
|
||||
|
||||
console.log('为按钮设置初始样式:', button.textContent.trim(), '索引:', index, '类名:', Array.from(button.classList).join(', '));
|
||||
|
||||
button.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
console.log('点击按钮:', button.textContent.trim(), '索引:', index);
|
||||
|
||||
// 检查是否是再次点击已选中的按钮
|
||||
const isActive = button.classList.contains('active');
|
||||
|
||||
// 重置所有按钮为非选中状态
|
||||
timeRangeButtons.forEach((btn, btnIndex) => {
|
||||
const btnStyle = buttonStyles[btnIndex % buttonStyles.length];
|
||||
|
||||
// 移除所有可能的激活状态类
|
||||
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500', 'bg-gray-500');
|
||||
btn.classList.remove(...btnStyle.active);
|
||||
btn.classList.remove(...btnStyle.activeHover);
|
||||
|
||||
// 添加非选中状态类
|
||||
btn.classList.add(...btnStyle.normal);
|
||||
btn.classList.add(...btnStyle.hover);
|
||||
});
|
||||
|
||||
if (isActive && index < 3) { // 再次点击已选中的时间范围按钮
|
||||
// 切换到混合视图
|
||||
isMixedView = true;
|
||||
currentTimeRange = 'mixed';
|
||||
console.log('切换到混合视图');
|
||||
|
||||
// 设置当前按钮为特殊混合视图状态(保持原按钮选中但添加混合视图标记)
|
||||
button.classList.remove(...styleConfig.normal);
|
||||
button.classList.remove(...styleConfig.hover);
|
||||
button.classList.add('active', 'mixed-view-active');
|
||||
button.classList.add(...styleConfig.active);
|
||||
button.classList.add(...styleConfig.activeHover); // 添加选中时的浅色悬停
|
||||
} else {
|
||||
// 普通选中模式
|
||||
isMixedView = false;
|
||||
lastSelectedIndex = index;
|
||||
|
||||
// 设置当前按钮为激活状态
|
||||
button.classList.remove(...styleConfig.normal);
|
||||
button.classList.remove(...styleConfig.hover);
|
||||
button.classList.add('active');
|
||||
button.classList.add(...styleConfig.active);
|
||||
button.classList.add(...styleConfig.activeHover); // 添加选中时的浅色悬停
|
||||
|
||||
// 获取并更新当前时间范围
|
||||
const rangeValue = button.dataset.range || button.textContent.trim().replace(/[^0-9a-zA-Z]/g, '');
|
||||
currentTimeRange = rangeValue;
|
||||
console.log('更新时间范围为:', currentTimeRange);
|
||||
}
|
||||
|
||||
// 重新加载数据
|
||||
loadDashboardData();
|
||||
// 更新DNS请求图表
|
||||
drawDNSRequestsChart();
|
||||
});
|
||||
|
||||
// 移除自定义鼠标悬停提示效果
|
||||
});
|
||||
|
||||
// 确保默认选中第一个按钮
|
||||
if (timeRangeButtons.length > 0) {
|
||||
const firstButton = timeRangeButtons[0];
|
||||
const firstStyle = buttonStyles[0];
|
||||
|
||||
// 先重置所有按钮
|
||||
timeRangeButtons.forEach((btn, index) => {
|
||||
const btnStyle = buttonStyles[index % buttonStyles.length];
|
||||
btn.classList.remove('active', 'bg-blue-500', 'text-white', 'bg-green-500', 'bg-purple-500');
|
||||
btn.classList.remove(...btnStyle.active);
|
||||
btn.classList.add(...btnStyle.normal);
|
||||
btn.classList.add(...btnStyle.hover);
|
||||
});
|
||||
|
||||
// 然后设置第一个按钮为激活状态
|
||||
firstButton.classList.remove(...firstStyle.normal);
|
||||
firstButton.classList.remove(...firstStyle.hover);
|
||||
firstButton.classList.add('active');
|
||||
firstButton.classList.add(...firstStyle.active);
|
||||
console.log('默认选中第一个按钮:', firstButton.textContent.trim());
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化图表
|
||||
@@ -567,72 +693,174 @@ function drawDNSRequestsChart() {
|
||||
}
|
||||
|
||||
const chartContext = ctx.getContext('2d');
|
||||
let apiFunction;
|
||||
|
||||
// 根据当前时间范围选择API函数
|
||||
switch (currentTimeRange) {
|
||||
case '7d':
|
||||
apiFunction = api.getDailyStats;
|
||||
break;
|
||||
case '30d':
|
||||
apiFunction = api.getMonthlyStats;
|
||||
break;
|
||||
default: // 24h
|
||||
apiFunction = api.getHourlyStats;
|
||||
}
|
||||
// 混合视图配置
|
||||
const datasetsConfig = [
|
||||
{ label: '24小时', api: (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] })), color: '#3b82f6', fillColor: 'rgba(59, 130, 246, 0.1)' },
|
||||
{ label: '7天', api: (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] })), color: '#22c55e', fillColor: 'rgba(34, 197, 94, 0.1)' },
|
||||
{ label: '30天', api: (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] })), color: '#a855f7', fillColor: 'rgba(168, 85, 247, 0.1)' }
|
||||
];
|
||||
|
||||
// 获取统计数据
|
||||
apiFunction().then(data => {
|
||||
// 创建或更新图表
|
||||
if (dnsRequestsChart) {
|
||||
dnsRequestsChart.data.labels = data.labels;
|
||||
dnsRequestsChart.data.datasets[0].data = data.data;
|
||||
dnsRequestsChart.update();
|
||||
} else {
|
||||
dnsRequestsChart = new Chart(chartContext, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: data.labels,
|
||||
datasets: [{
|
||||
label: 'DNS请求数量',
|
||||
data: data.data,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
}
|
||||
// 检查是否为混合视图
|
||||
if (isMixedView || currentTimeRange === 'mixed') {
|
||||
console.log('渲染混合视图图表');
|
||||
|
||||
// 显示图例
|
||||
const showLegend = true;
|
||||
|
||||
// 获取所有时间范围的数据
|
||||
Promise.all(datasetsConfig.map(config =>
|
||||
config.api().catch(error => {
|
||||
console.error(`获取${config.label}数据失败,使用模拟数据:`, error);
|
||||
// 返回模拟数据
|
||||
return {
|
||||
labels: generateTimeLabels(config.label === '24小时' ? 24 : (config.label === '7天' ? 7 : 30)),
|
||||
data: generateMockData(config.label === '24小时' ? 24 : (config.label === '7天' ? 7 : 30), 100, 1000)
|
||||
};
|
||||
})
|
||||
)).then(results => {
|
||||
// 创建数据集
|
||||
const datasets = results.map((data, index) => ({
|
||||
label: datasetsConfig[index].label,
|
||||
data: data.data,
|
||||
borderColor: datasetsConfig[index].color,
|
||||
backgroundColor: datasetsConfig[index].fillColor,
|
||||
tension: 0.4,
|
||||
fill: false, // 混合视图不填充
|
||||
borderWidth: 2
|
||||
}));
|
||||
|
||||
// 创建或更新图表
|
||||
if (dnsRequestsChart) {
|
||||
dnsRequestsChart.data.labels = results[0].labels; // 使用第一个数据集的标签
|
||||
dnsRequestsChart.data.datasets = datasets;
|
||||
dnsRequestsChart.options.plugins.legend.display = showLegend;
|
||||
dnsRequestsChart.update();
|
||||
} else {
|
||||
dnsRequestsChart = new Chart(chartContext, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: results[0].labels,
|
||||
datasets: datasets
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.1)'
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: showLegend,
|
||||
position: 'top'
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
}
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.1)'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('绘制混合视图图表失败:', error);
|
||||
});
|
||||
} else {
|
||||
// 普通视图
|
||||
// 根据当前时间范围选择API函数
|
||||
switch (currentTimeRange) {
|
||||
case '7d':
|
||||
apiFunction = (api && api.getDailyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
break;
|
||||
case '30d':
|
||||
apiFunction = (api && api.getMonthlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
break;
|
||||
default: // 24h
|
||||
apiFunction = (api && api.getHourlyStats) || (() => Promise.resolve({ labels: [], data: [] }));
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('绘制DNS请求图表失败:', error);
|
||||
});
|
||||
|
||||
// 获取统计数据
|
||||
apiFunction().then(data => {
|
||||
// 创建或更新图表
|
||||
if (dnsRequestsChart) {
|
||||
dnsRequestsChart.data.labels = data.labels;
|
||||
dnsRequestsChart.data.datasets = [{
|
||||
label: 'DNS请求数量',
|
||||
data: data.data,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}];
|
||||
dnsRequestsChart.options.plugins.legend.display = false;
|
||||
dnsRequestsChart.update();
|
||||
} else {
|
||||
dnsRequestsChart = new Chart(chartContext, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: data.labels,
|
||||
datasets: [{
|
||||
label: 'DNS请求数量',
|
||||
data: data.data,
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
mode: 'index',
|
||||
intersect: false
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.1)'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('绘制DNS请求图表失败:', error);
|
||||
// 使用模拟数据
|
||||
const mockData = {
|
||||
labels: generateTimeLabels(currentTimeRange === '24h' ? 24 : (currentTimeRange === '7d' ? 7 : 30)),
|
||||
data: generateMockData(currentTimeRange === '24h' ? 24 : (currentTimeRange === '7d' ? 7 : 30), 100, 1000)
|
||||
};
|
||||
|
||||
if (dnsRequestsChart) {
|
||||
dnsRequestsChart.data.labels = mockData.labels;
|
||||
dnsRequestsChart.data.datasets[0].data = mockData.data;
|
||||
dnsRequestsChart.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图表数据
|
||||
|
||||
Reference in New Issue
Block a user