Files
pdf-translate-web/templates/admin/dashboard.html
coder db98c2b82c refactor: 备用大模型整合到大模型配置页
- 备用大模型不再单独页面,直接在大模型配置页显示
- 新增/编辑使用模态框,更简洁高效
- 移除独立的 backup_llm.html 和 backup_llm_form.html
- 统一侧边栏导航,移除备用大模型链接
- API 改为只返回 JSON,不再渲染页面
2026-04-16 15:58:17 +08:00

267 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 rel="icon" href="/static/img/favicon.svg" type="image/svg+xml">
<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.packages') }}"><i class="bi bi-box-seam"></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.llm_config') }}"><i class="bi bi-cpu"></i> 大模型配置</a></li>
<li class="nav-item"><a class="nav-link " href="{{ url_for('admin.user_types') }}"><i class="bi bi-person-badge"></i> 用户类型</a></li>
<li class="nav-item"><a class="nav-link " href="{{ url_for('admin.membership_plans') }}"><i class="bi bi-credit-card"></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>