Files
pdf-translate-web/templates/admin/dashboard.html
coder 7fede0212b feat: 后台添加大模型配置管理页面
- 新增 /admin/llm_config 页面
- 支持配置API地址、Key、模型名称、参数
- 支持测试连接和恢复默认配置
- 配置保存到数据库,翻译服务动态读取
- 所有后台页面侧边栏添加入口
2026-04-10 18:42:20 +08:00

299 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.llm_config') }}">
<i class="bi bi-cpu"></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>