2024-11-14 14:55:43 +00:00
|
|
|
|
layui.use(['layer'], function(){
|
|
|
|
|
var layer = layui.layer;
|
|
|
|
|
var $ = layui.$;
|
|
|
|
|
|
2024-11-16 15:59:15 +00:00
|
|
|
|
// 初始化图表
|
|
|
|
|
var registerTrendChart = echarts.init(document.getElementById('register-trend'));
|
|
|
|
|
var deviceTypesChart = echarts.init(document.getElementById('device-types'));
|
|
|
|
|
|
2024-11-14 14:55:43 +00:00
|
|
|
|
// 加载统计数据
|
|
|
|
|
function loadStats() {
|
|
|
|
|
fetch('/api/dashboard/stats', {
|
2024-11-16 15:59:15 +00:00
|
|
|
|
credentials: 'include',
|
|
|
|
|
headers: {
|
|
|
|
|
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
|
|
|
|
}
|
2024-11-14 14:55:43 +00:00
|
|
|
|
})
|
|
|
|
|
.then(response => {
|
|
|
|
|
if (response.status === 401) {
|
|
|
|
|
window.location.href = '/login';
|
|
|
|
|
throw new Error('认证失败');
|
|
|
|
|
}
|
|
|
|
|
return response.json();
|
|
|
|
|
})
|
|
|
|
|
.then(result => {
|
|
|
|
|
if (result.error) {
|
|
|
|
|
layer.msg(result.error);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新统计数据
|
2024-11-16 15:59:15 +00:00
|
|
|
|
updateStats(result.data);
|
|
|
|
|
// 更新图表
|
|
|
|
|
updateCharts(result.data);
|
|
|
|
|
// 更新系统状态
|
|
|
|
|
updateSystemStatus(result.data);
|
2024-11-14 14:55:43 +00:00
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
layer.msg('加载统计数据失败:' + error.message);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-16 15:59:15 +00:00
|
|
|
|
// 更新统计数据
|
|
|
|
|
function updateStats(data) {
|
|
|
|
|
$('#total-devices').text(data.total_devices);
|
|
|
|
|
$('#total-licenses').text(data.total_licenses);
|
|
|
|
|
$('#online-devices').text(data.online_devices);
|
|
|
|
|
$('#expired-devices').text(data.expired_devices);
|
|
|
|
|
$('#today-new').text(data.today_new);
|
|
|
|
|
$('#unused-licenses').text(data.unused_licenses);
|
|
|
|
|
|
|
|
|
|
// 计算比率
|
|
|
|
|
var activeRate = data.total_devices > 0 ?
|
|
|
|
|
((data.online_devices / data.total_devices) * 100).toFixed(1) : 0;
|
|
|
|
|
var expiredRate = data.total_devices > 0 ?
|
|
|
|
|
((data.expired_devices / data.total_devices) * 100).toFixed(1) : 0;
|
|
|
|
|
|
|
|
|
|
$('#active-rate').text(activeRate + '%');
|
|
|
|
|
$('#expired-rate').text(expiredRate + '%');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新图表
|
|
|
|
|
function updateCharts(data) {
|
|
|
|
|
// 设备注册趋势图
|
|
|
|
|
var trendOption = {
|
|
|
|
|
title: {
|
|
|
|
|
text: '最近7天设备注册趋势'
|
|
|
|
|
},
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'axis'
|
|
|
|
|
},
|
|
|
|
|
xAxis: {
|
|
|
|
|
type: 'category',
|
|
|
|
|
data: data.trend_dates
|
|
|
|
|
},
|
|
|
|
|
yAxis: {
|
|
|
|
|
type: 'value'
|
|
|
|
|
},
|
|
|
|
|
series: [{
|
|
|
|
|
name: '新增设备',
|
|
|
|
|
type: 'line',
|
|
|
|
|
smooth: true,
|
|
|
|
|
data: data.trend_counts,
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: '#009688'
|
|
|
|
|
},
|
|
|
|
|
areaStyle: {
|
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
|
|
|
|
offset: 0,
|
|
|
|
|
color: 'rgba(0, 150, 136, 0.3)'
|
|
|
|
|
}, {
|
|
|
|
|
offset: 1,
|
|
|
|
|
color: 'rgba(0, 150, 136, 0.1)'
|
|
|
|
|
}])
|
|
|
|
|
}
|
|
|
|
|
}]
|
|
|
|
|
};
|
|
|
|
|
registerTrendChart.setOption(trendOption);
|
|
|
|
|
|
|
|
|
|
// 设备类型分布图
|
|
|
|
|
var typesOption = {
|
|
|
|
|
title: {
|
|
|
|
|
text: '设备类型分布'
|
|
|
|
|
},
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
formatter: '{b}: {c} ({d}%)'
|
|
|
|
|
},
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'pie',
|
|
|
|
|
radius: ['50%', '70%'],
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
itemStyle: {
|
|
|
|
|
borderRadius: 10,
|
|
|
|
|
borderColor: '#fff',
|
|
|
|
|
borderWidth: 2
|
|
|
|
|
},
|
|
|
|
|
label: {
|
|
|
|
|
show: false,
|
|
|
|
|
position: 'center'
|
|
|
|
|
},
|
|
|
|
|
emphasis: {
|
|
|
|
|
label: {
|
|
|
|
|
show: true,
|
|
|
|
|
fontSize: '20',
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
labelLine: {
|
|
|
|
|
show: false
|
|
|
|
|
},
|
|
|
|
|
data: data.device_types.map(item => ({
|
|
|
|
|
name: item.type,
|
|
|
|
|
value: item.count
|
|
|
|
|
}))
|
|
|
|
|
}]
|
|
|
|
|
};
|
|
|
|
|
deviceTypesChart.setOption(typesOption);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新系统状态
|
|
|
|
|
function updateSystemStatus(data) {
|
|
|
|
|
$('#cpu-usage').text(data.cpu_usage + '%');
|
|
|
|
|
$('#memory-usage').text(data.memory_usage + '%');
|
|
|
|
|
$('#disk-usage').text(data.disk_usage + '%');
|
|
|
|
|
$('#uptime').text(formatDuration(data.uptime));
|
|
|
|
|
$('#load-avg').text(data.load_avg.join(' '));
|
|
|
|
|
$('#network-traffic').text(formatBytes(data.network_traffic) + '/s');
|
|
|
|
|
$('#online-users').text(data.online_users);
|
|
|
|
|
$('#last-update').text(new Date().toLocaleString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
function formatDuration(seconds) {
|
|
|
|
|
var days = Math.floor(seconds / 86400);
|
|
|
|
|
var hours = Math.floor((seconds % 86400) / 3600);
|
|
|
|
|
var minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
|
var parts = [];
|
|
|
|
|
if (days > 0) parts.push(days + '天');
|
|
|
|
|
if (hours > 0) parts.push(hours + '小时');
|
|
|
|
|
if (minutes > 0) parts.push(minutes + '分钟');
|
|
|
|
|
return parts.join(' ') || '0分钟';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 格式化字节大小
|
|
|
|
|
function formatBytes(bytes) {
|
|
|
|
|
if (bytes === 0) return '0 B';
|
|
|
|
|
var k = 1024;
|
|
|
|
|
var sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
|
|
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
|
|
|
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-14 14:55:43 +00:00
|
|
|
|
// 初始加载
|
|
|
|
|
loadStats();
|
|
|
|
|
|
|
|
|
|
// 定时刷新(每30秒)
|
|
|
|
|
setInterval(loadStats, 30000);
|
2024-11-16 15:59:15 +00:00
|
|
|
|
|
|
|
|
|
// 窗口大小改变时重绘图表
|
|
|
|
|
window.onresize = function() {
|
|
|
|
|
registerTrendChart.resize();
|
|
|
|
|
deviceTypesChart.resize();
|
|
|
|
|
};
|
2024-11-14 14:55:43 +00:00
|
|
|
|
});
|