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

399 lines
14 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="active-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="online-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="today-activations">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-info rounded-circle shadow">
<i class="fas fa-user-plus"></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">激活趋势最近7天</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="licenseTypeChart" height="300"></canvas>
</div>
</div>
</div>
</div>
<!-- 快捷操作 -->
<div class="row">
<div class="col-lg-12">
<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="row">
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.create_product') }}" class="btn btn-outline-primary w-100">
<i class="fas fa-plus me-2"></i>
创建产品
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.generate_license') }}" class="btn btn-outline-success w-100">
<i class="fas fa-key me-2"></i>
生成卡密
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.create_version') }}" class="btn btn-outline-info w-100">
<i class="fas fa-code-branch me-2"></i>
发布版本
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.import_license') }}" class="btn btn-outline-warning w-100">
<i class="fas fa-file-import me-2"></i>
导入卡密
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 最近活动 -->
<div class="row mt-4">
<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="pending-tickets">
<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, licenseTypeChart;
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
loadDashboardData();
initCharts();
});
// 加载仪表板数据
function loadDashboardData() {
// 加载总览统计
apiRequest('/api/v1/statistics/overview')
.then(data => {
if (data.success) {
const stats = data.data;
document.getElementById('total-products').textContent = stats.products.total;
document.getElementById('active-licenses').textContent = stats.licenses.active;
document.getElementById('online-devices').textContent = stats.devices.online;
document.getElementById('today-activations').textContent = stats.activations.today;
// 更新卡密类型图表
if (licenseTypeChart) {
licenseTypeChart.data.datasets[0].data = [
stats.licenses.trial,
stats.licenses.formal
];
licenseTypeChart.update();
}
}
})
.catch(error => {
console.error('Failed to load dashboard data:', error);
});
// 加载激活趋势
apiRequest('/api/v1/statistics/activations?days=7')
.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);
});
// 加载最近激活
loadRecentActivations();
// 加载待处理工单
loadPendingTickets();
}
// 初始化图表
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 licenseTypeCtx = document.getElementById('licenseTypeChart').getContext('2d');
licenseTypeChart = new Chart(licenseTypeCtx, {
type: 'doughnut',
data: {
labels: ['试用卡密', '正式卡密'],
datasets: [{
data: [0, 0],
backgroundColor: [
'rgba(255, 206, 86, 0.8)',
'rgba(54, 162, 235, 0.8)'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
// 加载最近激活
function loadRecentActivations() {
apiRequest('/api/v1/licenses?status=1&per_page=5')
.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 loadPendingTickets() {
apiRequest('/api/v1/tickets?status=0&per_page=5')
.then(data => {
if (data.success) {
const tbody = document.getElementById('pending-tickets');
tbody.innerHTML = '';
if (data.data.tickets.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted">暂无数据</td></tr>';
return;
}
data.data.tickets.forEach(ticket => {
const priorityBadge = {
0: '<span class="badge bg-secondary">低</span>',
1: '<span class="badge bg-warning">中</span>',
2: '<span class="badge bg-danger">高</span>'
};
const row = `
<tr>
<td>
<a href="/tickets/${ticket.ticket_id}" class="text-decoration-none">
${ticket.title}
</a>
</td>
<td>${priorityBadge[ticket.priority]}</td>
<td>${formatDate(ticket.create_time)}</td>
</tr>
`;
tbody.innerHTML += row;
});
}
})
.catch(error => {
console.error('Failed to load pending tickets:', error);
});
}
// 定期刷新数据
setInterval(loadDashboardData, 60000); // 每分钟刷新一次
</script>
{% endblock %}