修复config.go中的URL错误
This commit is contained in:
@@ -19,6 +19,15 @@ body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 基础响应式变量 */
|
||||
:root {
|
||||
--sidebar-width: 250px;
|
||||
--sidebar-mobile-width: 70px;
|
||||
--header-height: 130px;
|
||||
--content-padding: 1rem;
|
||||
--card-min-width: 300px;
|
||||
}
|
||||
|
||||
/* 主容器样式 */
|
||||
.container {
|
||||
display: flex;
|
||||
@@ -70,17 +79,79 @@ header p {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
width: var(--sidebar-width);
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
height: calc(100vh - 130px); /* 减去header的高度 */
|
||||
height: calc(100vh - var(--header-height)); /* 减去header的高度 */
|
||||
transition: width 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 移动设备侧边栏切换按钮 */
|
||||
.sidebar-toggle {
|
||||
position: fixed;
|
||||
top: calc(var(--header-height) + 10px);
|
||||
left: 10px;
|
||||
z-index: 100;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 响应式布局 - 平板设备 */
|
||||
@media (max-width: 992px) {
|
||||
.sidebar {
|
||||
width: var(--sidebar-mobile-width);
|
||||
}
|
||||
|
||||
.nav-item span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式布局 - 移动设备 */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: -var(--sidebar-width);
|
||||
top: var(--header-height);
|
||||
z-index: 99;
|
||||
height: calc(100vh - var(--header-height));
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
left: 0;
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.sidebar.open .nav-item span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar.open .nav-item i {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
@@ -114,11 +185,39 @@ header p {
|
||||
/* 主内容区域样式 */
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
padding: var(--content-padding);
|
||||
overflow-y: auto;
|
||||
background-color: #f8f9fa;
|
||||
min-width: 0; /* 防止flex子元素溢出 */
|
||||
height: calc(100vh - 130px); /* 减去header的高度 */
|
||||
height: calc(100vh - var(--header-height)); /* 减去header的高度 */
|
||||
transition: padding-left 0.3s ease;
|
||||
}
|
||||
|
||||
/* 平板设备适配 - 侧边栏折叠时调整内容区域 */
|
||||
@media (max-width: 992px) {
|
||||
.content {
|
||||
padding-left: calc(var(--content-padding) + 10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 移动设备适配 - 侧边栏隐藏时的内容区域 */
|
||||
@media (max-width: 768px) {
|
||||
.content {
|
||||
padding-left: var(--content-padding);
|
||||
}
|
||||
|
||||
/* 响应式头部样式 */
|
||||
header.header-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 面板样式 */
|
||||
@@ -258,18 +357,30 @@ header p {
|
||||
/* 统计卡片网格 */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
|
||||
gap: clamp(1rem, 3vw, 1.5rem); /* 根据屏幕宽度动态调整间距 */
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* 图表容器 */
|
||||
.charts-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
|
||||
gap: clamp(1rem, 3vw, 1.5rem); /* 根据屏幕宽度动态调整间距 */
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
padding: clamp(1rem, 3vw, 1.5rem); /* 根据屏幕宽度动态调整内边距 */
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
min-width: 0; /* 防止内容溢出 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
@@ -277,6 +388,49 @@ header p {
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 卡片布局的响应式优化 */
|
||||
@media (max-width: 640px) {
|
||||
/* 在极小屏幕上,调整卡片网格为单列显示 */
|
||||
.stats-grid,
|
||||
.charts-container,
|
||||
.tables-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* 卡片更紧凑的内边距 */
|
||||
.stat-card,
|
||||
.chart-card,
|
||||
.table-card {
|
||||
padding: 1rem;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* 优化统计卡片的图标大小 */
|
||||
.stat-card i {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* 优化统计卡片的数值和标签 */
|
||||
.stat-value {
|
||||
font-size: clamp(1.2rem, 5vw, 1.5rem);
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: clamp(0.7rem, 3vw, 0.8rem);
|
||||
}
|
||||
|
||||
/* 优化图表卡片标题 */
|
||||
.chart-card h3 {
|
||||
font-size: clamp(1rem, 4vw, 1.1rem);
|
||||
}
|
||||
|
||||
/* 优化面板标题 */
|
||||
.panel-header h2 {
|
||||
font-size: clamp(1.2rem, 5vw, 1.3rem);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card i {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
@@ -321,10 +475,61 @@ header p {
|
||||
/* 表格容器 */
|
||||
.tables-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
/* 表格卡片样式 */
|
||||
.table-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||
min-width: 0; /* 防止子元素溢出 */
|
||||
}
|
||||
|
||||
/* 表格响应式样式 */
|
||||
@media (max-width: 768px) {
|
||||
/* 调整卡片内边距 */
|
||||
.table-card,
|
||||
.stat-card,
|
||||
.chart-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* 调整表格单元格内边距 */
|
||||
th, td {
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* 调整表格卡片标题 */
|
||||
.table-card h3,
|
||||
.chart-card h3 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* 调整统计卡片数值和标签 */
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* 调整面板标题 */
|
||||
.panel-header h2 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
/* 调整按钮大小 */
|
||||
.btn {
|
||||
padding: 0.4rem 0.8rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
@@ -346,6 +551,7 @@ header p {
|
||||
margin-bottom: 16px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
-webkit-overflow-scrolling: touch; /* iOS平滑滚动 */
|
||||
}
|
||||
|
||||
table {
|
||||
@@ -353,12 +559,51 @@ table {
|
||||
border-collapse: collapse;
|
||||
background-color: #ffffff;
|
||||
margin: 0;
|
||||
table-layout: fixed; /* 固定布局,有助于响应式设计 */
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
word-break: break-word; /* 长文本自动换行 */
|
||||
}
|
||||
|
||||
/* 移动设备上表格的优化 */
|
||||
@media (max-width: 768px) {
|
||||
/* 确保表格可以水平滚动 */
|
||||
.table-wrapper {
|
||||
max-width: 100%;
|
||||
margin-left: -1rem;
|
||||
margin-right: -1rem;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 表格单元格内容截断处理 */
|
||||
td {
|
||||
font-size: 0.85rem;
|
||||
max-width: 150px; /* 限制单元格最大宽度 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 当用户触摸单元格时显示完整内容 */
|
||||
td:active {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 优化百分比条在小屏幕上的显示 */
|
||||
.count-cell {
|
||||
position: relative;
|
||||
padding-right: 50px; /* 为百分比文本留出空间 */
|
||||
}
|
||||
|
||||
.percentage-text {
|
||||
font-size: 10px;
|
||||
right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
|
||||
@@ -115,28 +115,71 @@ function fetchHostsCount() {
|
||||
// 空实现,保留函数声明以避免引用错误
|
||||
}
|
||||
|
||||
// 通用API请求函数
|
||||
function apiRequest(endpoint, method = 'GET', data = null) {
|
||||
// 通用API请求函数 - 添加错误处理和重试机制
|
||||
function apiRequest(endpoint, method = 'GET', data = null, maxRetries = 3) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const config = {
|
||||
method,
|
||||
headers
|
||||
headers,
|
||||
timeout: 10000, // 设置超时时间为10秒
|
||||
};
|
||||
|
||||
if (data && (method === 'POST' || method === 'PUT' || method === 'DELETE')) {
|
||||
config.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
return fetch(`${API_BASE_URL}${endpoint}`, config)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
let retries = 0;
|
||||
|
||||
function makeRequest() {
|
||||
return fetch(`${API_BASE_URL}${endpoint}`, config)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// 检查响应是否完整
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
// 使用.text()先获取响应文本,处理可能的JSON解析错误
|
||||
return response.text().then(text => {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error('JSON解析错误:', e, '响应文本:', text);
|
||||
// 针对ERR_INCOMPLETE_CHUNKED_ENCODING错误进行重试
|
||||
if (retries < maxRetries) {
|
||||
retries++;
|
||||
console.warn(`请求失败,正在进行第${retries}次重试...`);
|
||||
return new Promise(resolve => setTimeout(() => resolve(makeRequest()), 1000 * retries));
|
||||
}
|
||||
throw new Error('JSON解析失败且重试次数已达上限');
|
||||
}
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('API请求错误:', error);
|
||||
|
||||
// 检查是否为网络错误或ERR_INCOMPLETE_CHUNKED_ENCODING相关错误
|
||||
if ((error.name === 'TypeError' && error.message.includes('Failed to fetch')) ||
|
||||
error.message.includes('incomplete chunked encoding')) {
|
||||
|
||||
if (retries < maxRetries) {
|
||||
retries++;
|
||||
console.warn(`网络错误,正在进行第${retries}次重试...`);
|
||||
return new Promise(resolve => setTimeout(() => resolve(makeRequest()), 1000 * retries));
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
return makeRequest();
|
||||
}
|
||||
|
||||
// 数字格式化函数
|
||||
|
||||
@@ -6,6 +6,67 @@ let domainDataCache = {
|
||||
let domainUpdateTimer = null;
|
||||
const DOMAIN_UPDATE_INTERVAL = 5000; // 域名排行更新间隔,设为5秒,比统计数据更新慢一些
|
||||
|
||||
// 初始化小型图表 - 修复Canvas重用问题
|
||||
function initMiniCharts() {
|
||||
// 获取所有图表容器
|
||||
const chartContainers = document.querySelectorAll('.chart-card canvas');
|
||||
|
||||
// 全局图表实例存储
|
||||
window.chartInstances = window.chartInstances || {};
|
||||
|
||||
chartContainers.forEach(canvas => {
|
||||
// 获取图表数据属性
|
||||
const chartId = canvas.id;
|
||||
const chartType = canvas.dataset.chartType || 'line';
|
||||
const chartData = JSON.parse(canvas.dataset.chartData || '{}');
|
||||
|
||||
// 设置图表上下文
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 销毁已存在的图表实例,避免Canvas重用错误
|
||||
if (window.chartInstances[chartId]) {
|
||||
window.chartInstances[chartId].destroy();
|
||||
}
|
||||
|
||||
// 创建新图表
|
||||
window.chartInstances[chartId] = new Chart(ctx, {
|
||||
type: chartType,
|
||||
data: chartData,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
padding: 10,
|
||||
cornerRadius: 4
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)'
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
duration: 1000,
|
||||
easing: 'easeOutQuart'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化仪表盘面板
|
||||
function initDashboardPanel() {
|
||||
// 初始化小型图表
|
||||
@@ -20,6 +81,60 @@ function initDashboardPanel() {
|
||||
}
|
||||
// 启动域名排行的独立更新
|
||||
startDomainUpdate();
|
||||
|
||||
// 初始化响应式侧边栏
|
||||
initResponsiveSidebar();
|
||||
}
|
||||
|
||||
// 初始化响应式侧边栏
|
||||
function initResponsiveSidebar() {
|
||||
// 创建侧边栏切换按钮
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.className = 'sidebar-toggle';
|
||||
toggleBtn.innerHTML = '<i class="fas fa-bars"></i>';
|
||||
document.body.appendChild(toggleBtn);
|
||||
|
||||
// 侧边栏切换逻辑
|
||||
toggleBtn.addEventListener('click', function() {
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
sidebar.classList.toggle('open');
|
||||
|
||||
// 更新按钮图标
|
||||
const icon = toggleBtn.querySelector('i');
|
||||
if (sidebar.classList.contains('open')) {
|
||||
icon.className = 'fas fa-times';
|
||||
} else {
|
||||
icon.className = 'fas fa-bars';
|
||||
}
|
||||
});
|
||||
|
||||
// 在侧边栏打开时点击内容区域关闭侧边栏
|
||||
const content = document.querySelector('.content');
|
||||
content.addEventListener('click', function() {
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
const toggleBtn = document.querySelector('.sidebar-toggle');
|
||||
if (sidebar.classList.contains('open') && window.innerWidth <= 768) {
|
||||
sidebar.classList.remove('open');
|
||||
if (toggleBtn) {
|
||||
const icon = toggleBtn.querySelector('i');
|
||||
icon.className = 'fas fa-bars';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 窗口大小变化时调整侧边栏状态
|
||||
window.addEventListener('resize', function() {
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
const toggleBtn = document.querySelector('.sidebar-toggle');
|
||||
|
||||
if (window.innerWidth > 768) {
|
||||
sidebar.classList.remove('open');
|
||||
if (toggleBtn) {
|
||||
const icon = toggleBtn.querySelector('i');
|
||||
icon.className = 'fas fa-bars';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载仪表盘数据
|
||||
|
||||
Reference in New Issue
Block a user