功能特性: - 前端文章展示 (Markdown渲染、代码高亮) - 后台管理系统 (文章、分类、标签、作者管理) - 图片上传 (点击、拖拽、粘贴) - 网站设置 (Logo、底部宣传图片上传) - 访问统计 (IP记录、趋势图表) - 参考来源、附件支持 - 单端口部署 (16012) 技术栈: - Flask 3.0 + SQLAlchemy - SQLite数据库 - marked.js + highlight.js (Markdown渲染) - Chart.js (统计图表)
149 lines
3.6 KiB
HTML
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 %} |