Files
pdf-translate-web/templates/admin/dashboard.html
coder 2ef5e6da87 V2.0.0: 新增用户权限动态配置、会员套餐配置、数据包购买功能
新功能:
- 用户权限动态配置(翻译次数、页数限制)
- 会员套餐动态配置(名称、价格、周期)
- 数据包购买套餐管理
- 收入统计功能
- 数据包销售排行

技术更新:
- 新增 DynamicConfig 模型支持动态配置
- 新增 DataPackage 和 UserPackage 模型
- 后台管理增加数据包管理模块
2026-04-07 23:26:53 +08:00

294 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理 - PDF翻译助手</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body { background-color: #f5f5f5; }
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 250px;
background: #343a40;
padding-top: 60px;
z-index: 1000;
}
.sidebar .nav-link {
color: #adb5bd;
padding: 12px 20px;
border-left: 3px solid transparent;
}
.sidebar .nav-link:hover,
.sidebar .nav-link.active {
color: #fff;
background: rgba(255,255,255,0.1);
border-left-color: #0d6efd;
}
.sidebar .nav-link i { margin-right: 10px; }
.main-content {
margin-left: 250px;
padding: 20px;
}
.stat-card {
border-radius: 10px;
border: none;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.stat-card .icon {
width: 60px;
height: 60px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.stat-card .icon.blue { background: rgba(13, 110, 253, 0.1); color: #0d6efd; }
.stat-card .icon.green { background: rgba(25, 135, 84, 0.1); color: #198754; }
.stat-card .icon.orange { background: rgba(253, 126, 20, 0.1); color: #fd7e14; }
.stat-card .icon.purple { background: rgba(111, 66, 193, 0.1); color: #6f42c1; }
</style>
</head>
<body>
<!-- 侧边栏 -->
<nav class="sidebar">
<div class="position-absolute top-0 w-100 p-3 border-bottom border-secondary">
<h5 class="text-white mb-0">
<i class="bi bi-gear-fill"></i> 后台管理
</h5>
</div>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link active" href="{{ url_for('admin.dashboard') }}">
<i class="bi bi-speedometer2"></i> 数据概览
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.users') }}">
<i class="bi bi-people"></i> 用户管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.translations') }}">
<i class="bi bi-file-text"></i> 翻译记录
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.cache_list') }}">
<i class="bi bi-database"></i> 缓存管理
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.stats') }}">
<i class="bi bi-bar-chart"></i> 统计报表
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.logs') }}">
<i class="bi bi-list-check"></i> 操作日志
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('admin.settings') }}">
<i class="bi bi-sliders"></i> 系统配置
</a>
</li>
</ul>
<div class="position-absolute bottom-0 w-100 p-3 border-top border-secondary">
<a href="/" class="btn btn-outline-light btn-sm w-100">
<i class="bi bi-house"></i> 返回前台
</a>
</div>
</nav>
<!-- 主内容区 -->
<main class="main-content">
<!-- 统计卡片 -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card stat-card">
<div class="card-body d-flex align-items-center">
<div class="icon blue me-3"><i class="bi bi-people-fill"></i></div>
<div>
<div class="text-muted small">总用户数</div>
<div class="fs-4 fw-bold">{{ total_users }}</div>
<div class="text-success small">今日 +{{ new_users_today }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card">
<div class="card-body d-flex align-items-center">
<div class="icon green me-3"><i class="bi bi-file-earmark-text"></i></div>
<div>
<div class="text-muted small">总翻译次数</div>
<div class="fs-4 fw-bold">{{ total_translations }}</div>
<div class="text-success small">今日 +{{ today_translations }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card">
<div class="card-body d-flex align-items-center">
<div class="icon orange me-3"><i class="bi bi-crown"></i></div>
<div>
<div class="text-muted small">VIP用户</div>
<div class="fs-4 fw-bold">{{ vip_users }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card stat-card">
<div class="card-body d-flex align-items-center">
<div class="icon purple me-3"><i class="bi bi-database-fill"></i></div>
<div>
<div class="text-muted small">缓存数量</div>
<div class="fs-4 fw-bold">{{ total_cache }}</div>
<div class="text-info small">命中 {{ total_cache_hits }} 次</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- 每日翻译趋势 -->
<div class="col-md-8 mb-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0"><i class="bi bi-graph-up"></i> 每日翻译趋势最近7天</h6>
</div>
<div class="card-body">
<canvas id="trendChart" height="100"></canvas>
</div>
</div>
</div>
<!-- 快捷操作 -->
<div class="col-md-4 mb-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0"><i class="bi bi-lightning"></i> 快捷操作</h6>
</div>
<div class="card-body">
<a href="{{ url_for('admin.users') }}" class="btn btn-outline-primary w-100 mb-2">
<i class="bi bi-person-plus"></i> 添加用户
</a>
<a href="{{ url_for('admin.cache_list') }}" class="btn btn-outline-warning w-100 mb-2">
<i class="bi bi-trash"></i> 清理缓存
</a>
<a href="{{ url_for('admin.settings') }}" class="btn btn-outline-secondary w-100">
<i class="bi bi-gear"></i> 系统配置
</a>
</div>
</div>
</div>
</div>
<div class="row">
<!-- 最近翻译 -->
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header d-flex justify-content-between">
<h6 class="mb-0"><i class="bi bi-clock-history"></i> 最近翻译</h6>
<a href="{{ url_for('admin.translations') }}" class="small">查看全部</a>
</div>
<div class="card-body p-0">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>文件名</th>
<th>状态</th>
<th>时间</th>
</tr>
</thead>
<tbody>
{% for t in recent_translations %}
<tr>
<td>{{ t.original_filename[:30] }}{% if t.original_filename|length > 30 %}...{% endif %}</td>
<td>
<span class="badge bg-{% if t.status == 'completed' %}success{% elif t.status == 'processing' %}warning{% else %}danger{% endif %}">
{{ t.status }}
</span>
</td>
<td><small>{{ t.created_at.strftime('%H:%M') }}</small></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- 最近用户 -->
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header d-flex justify-content-between">
<h6 class="mb-0"><i class="bi bi-person-lines-fill"></i> 最近注册</h6>
<a href="{{ url_for('admin.users') }}" class="small">查看全部</a>
</div>
<div class="card-body p-0">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>用户名</th>
<th>类型</th>
<th>注册时间</th>
</tr>
</thead>
<tbody>
{% for u in recent_users %}
<tr>
<td>{{ u.username }}</td>
<td>
<span class="badge bg-{% if u.user_type == 'free' %}secondary{% elif 'vip' in u.user_type %}warning{% else %}primary{% endif %}">
{{ u.user_type }}
</span>
</td>
<td><small>{{ u.created_at.strftime('%m-%d %H:%M') }}</small></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 绘制趋势图
const ctx = document.getElementById('trendChart').getContext('2d');
const data = {{ daily_stats | tojson }};
new Chart(ctx, {
type: 'line',
data: {
labels: data.map(d => d.date),
datasets: [{
label: '翻译次数',
data: data.map(d => d.count),
borderColor: '#0d6efd',
backgroundColor: 'rgba(13, 110, 253, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } },
scales: { y: { beginAtZero: true } }
}
});
</script>
</body>
</html>