Files
tech-blog/app/templates/admin/stats_charts.html
hz4th_coder e689fed71e 初始化技术博客项目
功能特性:
- 前端文章展示 (Markdown渲染、代码高亮)
- 后台管理系统 (文章、分类、标签、作者管理)
- 图片上传 (点击、拖拽、粘贴)
- 网站设置 (Logo、底部宣传图片上传)
- 访问统计 (IP记录、趋势图表)
- 参考来源、附件支持
- 单端口部署 (16012)

技术栈:
- Flask 3.0 + SQLAlchemy
- SQLite数据库
- marked.js + highlight.js (Markdown渲染)
- Chart.js (统计图表)
2026-06-04 23:12:34 +08:00

149 lines
3.6 KiB
HTML

{% extends 'admin/base.html' %}
{% block title %}访问趋势{% endblock %}
{% block page_title %}访问趋势图表{% endblock %}
{% block content %}
<div class="stats-tabs">
<a href="{{ url_for('admin.stats') }}" class="tab">概览</a>
<a href="{{ url_for('admin.stats_charts') }}" class="tab active">趋势图表</a>
<a href="{{ url_for('admin.stats_logs') }}" class="tab">访问日志</a>
</div>
<div class="chart-container">
<h3>📈 最近30天访问趋势</h3>
<div class="chart-wrapper">
<canvas id="visitChart"></canvas>
</div>
</div>
<div class="chart-stats">
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-info">
<div class="stat-value">{{ stats.total_visits }}</div>
<div class="stat-label">30天总访问</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">📅</div>
<div class="stat-info">
<div class="stat-value">{{ stats.today_visits }}</div>
<div class="stat-label">今日访问</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🌐</div>
<div class="stat-info">
<div class="stat-value">{{ stats.unique_ips }}</div>
<div class="stat-label">独立访客</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const ctx = document.getElementById('visitChart').getContext('2d');
const data = {{ daily_data | tojson | safe }};
const reversedData = data.reverse();
new Chart(ctx, {
type: 'line',
data: {
labels: reversedData.map(d => d.date),
datasets: [
{
label: '总访问量',
data: reversedData.map(d => d.visits),
borderColor: '#3498db',
backgroundColor: 'rgba(52, 152, 219, 0.1)',
fill: true,
tension: 0.4
},
{
label: '独立访客',
data: reversedData.map(d => d.unique_ips),
borderColor: '#e74c3c',
backgroundColor: 'rgba(231, 76, 60, 0.1)',
fill: false,
tension: 0.4
},
{
label: '文章浏览',
data: reversedData.map(d => d.article_views),
borderColor: '#2ecc71',
backgroundColor: 'rgba(46, 204, 113, 0.1)',
fill: false,
tension: 0.4
}
]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'top'
}
},
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
<style>
.stats-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
background: var(--admin-card-bg);
border-radius: 5px;
color: var(--admin-text);
border: 1px solid var(--admin-border);
}
.tab:hover {
text-decoration: none;
background: var(--admin-primary);
color: #fff;
}
.tab.active {
background: var(--admin-primary);
color: #fff;
border-color: var(--admin-primary);
}
.chart-container {
background: var(--admin-card-bg);
border-radius: 8px;
padding: 25px;
margin-bottom: 25px;
box-shadow: var(--admin-shadow);
}
.chart-container h3 {
margin-bottom: 20px;
}
.chart-wrapper {
position: relative;
height: 300px;
}
.chart-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
</style>
{% endblock %}