2025-11-11 21:39:12 +08:00
|
|
|
|
{% extends "base.html" %}
|
|
|
|
|
|
|
|
|
|
|
|
{% block title %}仪表板 - 软件授权管理系统{% endblock %}
|
|
|
|
|
|
|
|
|
|
|
|
{% block page_title %}仪表板{% endblock %}
|
|
|
|
|
|
|
2025-12-27 15:12:05 +08:00
|
|
|
|
{% block extra_css %}
|
|
|
|
|
|
<style>
|
|
|
|
|
|
/* 改进的统计卡片样式 */
|
|
|
|
|
|
.card-stats {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 1rem;
|
|
|
|
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-stats::before {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
opacity: 0.1;
|
|
|
|
|
|
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-stats:hover {
|
|
|
|
|
|
transform: translateY(-5px);
|
|
|
|
|
|
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-stats .icon {
|
|
|
|
|
|
width: 60px;
|
|
|
|
|
|
height: 60px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
font-size: 1.5rem;
|
|
|
|
|
|
transition: transform 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
.card-stats:hover .icon {
|
|
|
|
|
|
transform: scale(1.1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 渐变背景 */
|
|
|
|
|
|
.gradient-bg-1 {
|
|
|
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gradient-bg-2 {
|
|
|
|
|
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gradient-bg-3 {
|
|
|
|
|
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
.gradient-bg-4 {
|
|
|
|
|
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 图表区域改进 */
|
|
|
|
|
|
.chart-container {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
height: 300px;
|
|
|
|
|
|
margin-bottom: 1rem;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 快捷操作按钮 */
|
|
|
|
|
|
.quick-action-btn {
|
|
|
|
|
|
border-radius: 0.75rem;
|
|
|
|
|
|
padding: 1.5rem;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
border: 2px solid;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
}
|
|
|
|
|
|
.quick-action-btn:hover {
|
|
|
|
|
|
transform: translateY(-3px);
|
|
|
|
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 最近活动卡片 */
|
|
|
|
|
|
.activity-card {
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-radius: 1rem;
|
|
|
|
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 动画效果 */
|
|
|
|
|
|
@keyframes fadeInUp {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateY(30px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
.fade-in-up {
|
|
|
|
|
|
animation: fadeInUp 0.6s ease-out;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
2025-11-11 21:39:12 +08:00
|
|
|
|
{% block content %}
|
|
|
|
|
|
<!-- 统计卡片 -->
|
|
|
|
|
|
<div class="row mb-4">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
|
|
|
|
<div class="card card-stats gradient-bg-1 fade-in-up">
|
2025-11-11 21:39:12 +08:00
|
|
|
|
<div class="card-body">
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<div class="col">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<h5 class="card-title text-uppercase text-white-50 mb-0">产品总数</h5>
|
|
|
|
|
|
<span class="h2 font-weight-bold mb-0 text-white" id="total-products">-</span>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-auto">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
|
|
|
|
|
<i class="fas fa-box text-white fs-4"></i>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
|
|
|
|
<div class="card card-stats gradient-bg-2 fade-in-up">
|
2025-11-11 21:39:12 +08:00
|
|
|
|
<div class="card-body">
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<div class="col">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<h5 class="card-title text-uppercase text-white-50 mb-0">活跃卡密</h5>
|
|
|
|
|
|
<span class="h2 font-weight-bold mb-0 text-white" id="active-licenses">-</span>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-auto">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
|
|
|
|
|
<i class="fas fa-key text-white fs-4"></i>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
|
|
|
|
<div class="card card-stats gradient-bg-3 fade-in-up">
|
2025-11-11 21:39:12 +08:00
|
|
|
|
<div class="card-body">
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<div class="col">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<h5 class="card-title text-uppercase text-white-50 mb-0">在线设备</h5>
|
|
|
|
|
|
<span class="h2 font-weight-bold mb-0 text-white" id="online-devices">-</span>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-auto">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
|
|
|
|
|
<i class="fas fa-desktop text-white fs-4"></i>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="col-xl-3 col-md-6 mb-4">
|
|
|
|
|
|
<div class="card card-stats gradient-bg-4 fade-in-up">
|
2025-11-11 21:39:12 +08:00
|
|
|
|
<div class="card-body">
|
|
|
|
|
|
<div class="row">
|
|
|
|
|
|
<div class="col">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<h5 class="card-title text-uppercase text-white-50 mb-0">今日激活</h5>
|
|
|
|
|
|
<span class="h2 font-weight-bold mb-0 text-white" id="today-activations">-</span>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-auto">
|
2025-12-27 15:12:05 +08:00
|
|
|
|
<div class="icon bg-white bg-opacity-20 rounded-circle shadow" style="width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;">
|
|
|
|
|
|
<i class="fas fa-user-plus text-white fs-4"></i>
|
2025-11-11 21:39:12 +08:00
|
|
|
|
</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() {
|
2025-11-15 23:57:05 +08:00
|
|
|
|
apiRequest('/api/v1/licenses?status=1&per_page=5&sort=activate_time&order=desc')
|
2025-11-11 21:39:12 +08:00
|
|
|
|
.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 %}
|