Kamixitong/app/web/templates/statistics/index.html
2025-11-11 21:39:12 +08:00

359 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}统计分析 - 软件授权管理系统{% endblock %}
{% block page_title %}统计分析{% endblock %}
{% block content %}
<!-- 统计卡片 -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">产品总数</h5>
<span class="h2 font-weight-bold mb-0" id="total-products">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-primary rounded-circle shadow">
<i class="fas fa-box"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">卡密总数</h5>
<span class="h2 font-weight-bold mb-0" id="total-licenses">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-warning rounded-circle shadow">
<i class="fas fa-key"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">设备总数</h5>
<span class="h2 font-weight-bold mb-0" id="total-devices">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-success rounded-circle shadow">
<i class="fas fa-desktop"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">工单总数</h5>
<span class="h2 font-weight-bold mb-0" id="total-tickets">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-info rounded-circle shadow">
<i class="fas fa-ticket-alt"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="row mb-4">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">激活趋势最近30天</h6>
</div>
<div class="card-body">
<canvas id="activationChart" height="300"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">产品分布</h6>
</div>
<div class="card-body">
<canvas id="productDistributionChart" height="300"></canvas>
</div>
</div>
</div>
</div>
<!-- 详细统计 -->
<div class="row">
<div class="col-lg-6">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">最近激活的卡密</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>卡密</th>
<th>产品</th>
<th>激活时间</th>
</tr>
</thead>
<tbody id="recent-activations">
<tr>
<td colspan="3" class="text-center text-muted">加载中...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">热门产品</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>产品名称</th>
<th>卡密数</th>
<th>设备数</th>
</tr>
</thead>
<tbody id="popular-products">
<tr>
<td colspan="3" class="text-center text-muted">加载中...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let activationChart, productDistributionChart;
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
loadStatistics();
initCharts();
});
// 加载统计数据
function loadStatistics() {
// 加载总览统计
apiRequest('/api/v1/statistics/overview')
.then(data => {
if (data.success) {
const stats = data.data;
document.getElementById('total-products').textContent = stats.products.total;
document.getElementById('total-licenses').textContent = stats.licenses.total;
document.getElementById('total-devices').textContent = stats.devices.total;
document.getElementById('total-tickets').textContent = stats.tickets.total;
}
})
.catch(error => {
console.error('Failed to load statistics:', error);
});
// 加载激活趋势
apiRequest('/api/v1/statistics/activations?days=30')
.then(data => {
if (data.success && activationChart) {
const labels = data.data.activations.map(item => {
const date = new Date(item.date);
return `${date.getMonth() + 1}/${date.getDate()}`;
});
const counts = data.data.activations.map(item => item.count);
activationChart.data.labels = labels;
activationChart.data.datasets[0].data = counts;
activationChart.update();
}
})
.catch(error => {
console.error('Failed to load activation trend:', error);
});
// 加载产品分布
apiRequest('/api/v1/statistics/products')
.then(data => {
if (data.success && productDistributionChart) {
const products = data.data.products.slice(0, 5); // 只显示前5个产品
const labels = products.map(p => p.product_name);
const counts = products.map(p => p.license_count);
productDistributionChart.data.labels = labels;
productDistributionChart.data.datasets[0].data = counts;
productDistributionChart.update();
}
})
.catch(error => {
console.error('Failed to load product distribution:', error);
});
// 加载最近激活
loadRecentActivations();
// 加载热门产品
loadPopularProducts();
}
// 初始化图表
function initCharts() {
// 激活趋势图表
const activationCtx = document.getElementById('activationChart').getContext('2d');
activationChart = new Chart(activationCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '激活数',
data: [],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
// 产品分布图表
const productDistributionCtx = document.getElementById('productDistributionChart').getContext('2d');
productDistributionChart = new Chart(productDistributionCtx, {
type: 'doughnut',
data: {
labels: [],
datasets: [{
data: [],
backgroundColor: [
'rgba(255, 99, 132, 0.8)',
'rgba(54, 162, 235, 0.8)',
'rgba(255, 206, 86, 0.8)',
'rgba(75, 192, 192, 0.8)',
'rgba(153, 102, 255, 0.8)'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
// 加载最近激活
function loadRecentActivations() {
apiRequest('/api/v1/licenses?status=1&per_page=5&sort=activate_time&order=desc')
.then(data => {
if (data.success) {
const tbody = document.getElementById('recent-activations');
tbody.innerHTML = '';
if (data.data.licenses.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted">暂无数据</td></tr>';
return;
}
data.data.licenses.forEach(license => {
const row = `
<tr>
<td><code>${license.license_key.substring(0, 8)}...</code></td>
<td>${license.product_name || '-'}</td>
<td>${formatDate(license.activate_time)}</td>
</tr>
`;
tbody.innerHTML += row;
});
}
})
.catch(error => {
console.error('Failed to load recent activations:', error);
});
}
// 加载热门产品
function loadPopularProducts() {
apiRequest('/api/v1/statistics/products')
.then(data => {
if (data.success) {
const tbody = document.getElementById('popular-products');
tbody.innerHTML = '';
if (data.data.products.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted">暂无数据</td></tr>';
return;
}
// 显示前5个产品
data.data.products.slice(0, 5).forEach(product => {
const row = `
<tr>
<td>${product.product_name}</td>
<td>${product.license_count}</td>
<td>${product.device_count}</td>
</tr>
`;
tbody.innerHTML += row;
});
}
})
.catch(error => {
console.error('Failed to load popular products:', error);
});
}
</script>
{% endblock %}