完成后台管理系统界面优化
This commit is contained in:
@@ -54,6 +54,7 @@ def init_admin(app):
|
||||
# 注册登录路由
|
||||
app.add_url_rule('/admin/login', 'admin.login', admin_login, methods=['GET', 'POST'])
|
||||
app.add_url_rule('/admin/logout', 'admin.logout', admin_logout)
|
||||
app.add_url_rule('/admin/logout/confirm', 'admin.logout_confirm', admin_logout_confirm)
|
||||
|
||||
return admin
|
||||
|
||||
@@ -80,9 +81,16 @@ def admin_login():
|
||||
def admin_logout():
|
||||
"""管理员登出"""
|
||||
logout_user()
|
||||
flash('已成功登出', 'success')
|
||||
flash('已成功登出后台管理系统', 'success')
|
||||
return redirect(url_for('admin.login'))
|
||||
|
||||
def admin_logout_confirm():
|
||||
"""管理员登出确认"""
|
||||
if not current_user.is_authenticated:
|
||||
return redirect(url_for('admin.login'))
|
||||
|
||||
return render_template('admin/logout_confirm.html')
|
||||
|
||||
class AdminIndexView(BaseView):
|
||||
"""管理后台首页"""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,8 @@
|
||||
{% extends 'admin/master.html' %}
|
||||
|
||||
{% block title %}数据分析 - 仪表板{% endblock %}
|
||||
{% block title %}数据分析{% endblock %}
|
||||
|
||||
{% block page_title %}数据分析{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
@@ -9,117 +11,170 @@
|
||||
|
||||
{% block body %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1 class="mb-4">
|
||||
<i class="fas fa-chart-line"></i> 数据分析仪表板
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-primary shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
|
||||
总用户数
|
||||
</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.total_users }}</div>
|
||||
<div class="text-xs text-muted">今日新增: {{ stats.new_users_today }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-users fa-2x text-gray-300"></i>
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-users text-primary" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-primary">{{ stats.total_users if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">总用户数</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-primary" style="width: 75%"></div>
|
||||
</div>
|
||||
<small class="text-muted">今日新增: {{ stats.new_users_today if stats else 0 }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-success shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
|
||||
总提示词
|
||||
</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.total_prompts }}</div>
|
||||
<div class="text-xs text-muted">今日生成: {{ stats.today_prompts }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-magic fa-2x text-gray-300"></i>
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-magic text-success" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-success">{{ stats.total_prompts if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">总提示词</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-success" style="width: 85%"></div>
|
||||
</div>
|
||||
<small class="text-muted">今日生成: {{ stats.today_prompts if stats else 0 }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-info shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
|
||||
活跃用户
|
||||
</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.active_users }}</div>
|
||||
<div class="text-xs text-muted">本周活跃: {{ stats.active_users_week }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-user-check fa-2x text-gray-300"></i>
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-user-check text-info" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-info">{{ stats.active_users if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">活跃用户</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-info" style="width: 60%"></div>
|
||||
</div>
|
||||
<small class="text-muted">本周活跃: {{ stats.active_users_week if stats else 0 }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-warning shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
|
||||
模板数量
|
||||
</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ stats.total_templates }}</div>
|
||||
<div class="text-xs text-muted">默认模板: {{ stats.default_templates }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-clipboard-list fa-2x text-gray-300"></i>
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-clipboard-list text-warning" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-warning">{{ stats.total_templates if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">模板数量</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-warning" style="width: 45%"></div>
|
||||
</div>
|
||||
<small class="text-muted">默认模板: {{ stats.default_templates if stats else 0 }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 趋势图表 -->
|
||||
<!-- 图表区域 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-8 col-lg-7">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-chart-area"></i> 用户注册趋势
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-chart-area me-2"></i> 用户注册趋势
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="userTrendChart" style="height: 300px;"></div>
|
||||
<div id="userTrendChart" style="height: 350px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 col-lg-5">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-chart-pie"></i> 用户状态分布
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-chart-pie me-2"></i> 用户状态分布
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="userStatusChart" style="height: 300px;"></div>
|
||||
<div id="userStatusChart" style="height: 350px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细分析 -->
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-chart-bar me-2"></i> 提示词生成趋势
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="promptTrendChart" style="height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-clock me-2"></i> 用户活跃度
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="userActivityChart" style="height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据洞察 -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-lightbulb me-2"></i> 数据洞察
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="insight-card text-center p-4">
|
||||
<div class="insight-icon mb-3">
|
||||
<i class="fas fa-arrow-up text-success fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="insight-title">用户增长</h5>
|
||||
<p class="insight-desc">本月用户增长率达到 <strong class="text-success">15%</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="insight-card text-center p-4">
|
||||
<div class="insight-icon mb-3">
|
||||
<i class="fas fa-chart-line text-primary fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="insight-title">使用频率</h5>
|
||||
<p class="insight-desc">平均每个用户每天使用 <strong class="text-primary">3.2</strong> 次</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="insight-card text-center p-4">
|
||||
<div class="insight-icon mb-3">
|
||||
<i class="fas fa-users text-info fa-2x"></i>
|
||||
</div>
|
||||
<h5 class="insight-title">用户留存</h5>
|
||||
<p class="insight-desc">7天用户留存率达到 <strong class="text-info">68%</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,32 +183,32 @@
|
||||
<!-- 快速操作 -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-bolt"></i> 快速操作
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-bolt me-2"></i> 快速操作
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('analytics_admin.charts') }}" class="btn btn-primary btn-block">
|
||||
<i class="fas fa-chart-bar"></i> 详细图表
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-3">
|
||||
<a href="{{ url_for('analytics_admin.charts') }}" class="btn btn-outline-primary btn-block">
|
||||
<i class="fas fa-chart-bar me-2"></i> 详细图表
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('admin_user.index_view') }}" class="btn btn-success btn-block">
|
||||
<i class="fas fa-users"></i> 用户管理
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-3">
|
||||
<a href="{{ url_for('admin_user.index_view') }}" class="btn btn-outline-success btn-block">
|
||||
<i class="fas fa-users me-2"></i> 用户管理
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('admin_prompt.index_view') }}" class="btn btn-info btn-block">
|
||||
<i class="fas fa-magic"></i> 提示词管理
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-3">
|
||||
<a href="{{ url_for('admin_prompt.index_view') }}" class="btn btn-outline-info btn-block">
|
||||
<i class="fas fa-magic me-2"></i> 提示词管理
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('admin_system.index') }}" class="btn btn-warning btn-block">
|
||||
<i class="fas fa-cogs"></i> 系统管理
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-3">
|
||||
<a href="{{ url_for('admin_system.index') }}" class="btn btn-outline-warning btn-block">
|
||||
<i class="fas fa-cogs me-2"></i> 系统管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,30 +219,48 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.border-left-primary {
|
||||
border-left: 0.25rem solid #4e73df !important;
|
||||
.stats-number {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.border-left-success {
|
||||
border-left: 0.25rem solid #1cc88a !important;
|
||||
|
||||
.stats-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.border-left-info {
|
||||
border-left: 0.25rem solid #36b9cc !important;
|
||||
|
||||
.progress {
|
||||
border-radius: 1rem;
|
||||
background-color: #f8f9fc;
|
||||
}
|
||||
.border-left-warning {
|
||||
border-left: 0.25rem solid #f6c23e !important;
|
||||
|
||||
.progress-bar {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
.text-gray-300 {
|
||||
color: #dddfeb !important;
|
||||
|
||||
.insight-card {
|
||||
border: 1px solid #e3e6f0;
|
||||
border-radius: 0.75rem;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
.text-gray-800 {
|
||||
color: #5a5c69 !important;
|
||||
|
||||
.insight-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
.font-weight-bold {
|
||||
font-weight: 700 !important;
|
||||
|
||||
.insight-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.text-xs {
|
||||
font-size: 0.7rem;
|
||||
|
||||
.insight-desc {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -195,8 +268,37 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 模拟图表数据(实际项目中会从后端获取)
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化图表
|
||||
initCharts();
|
||||
|
||||
// 数字动画
|
||||
const statsNumbers = document.querySelectorAll('.stats-number');
|
||||
statsNumbers.forEach(number => {
|
||||
const finalValue = parseInt(number.textContent);
|
||||
animateNumber(number, 0, finalValue, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
function animateNumber(element, start, end, duration) {
|
||||
const startTime = performance.now();
|
||||
|
||||
function updateNumber(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
const current = Math.floor(start + (end - start) * progress);
|
||||
element.textContent = current;
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(updateNumber);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(updateNumber);
|
||||
}
|
||||
|
||||
function initCharts() {
|
||||
// 用户注册趋势图
|
||||
var userTrendData = [{
|
||||
x: ['2025-08-20', '2025-08-21', '2025-08-22', '2025-08-23', '2025-08-24', '2025-08-25', '2025-08-26'],
|
||||
@@ -205,35 +307,110 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
mode: 'lines+markers',
|
||||
name: '注册用户',
|
||||
line: {color: '#4e73df', width: 3},
|
||||
marker: {size: 8}
|
||||
marker: {size: 8, color: '#4e73df'}
|
||||
}];
|
||||
|
||||
var userTrendLayout = {
|
||||
title: '用户注册趋势',
|
||||
xaxis: {title: '日期'},
|
||||
yaxis: {title: '注册人数'},
|
||||
height: 300,
|
||||
margin: {l: 50, r: 50, t: 50, b: 50}
|
||||
title: {text: '用户注册趋势', font: {size: 16}},
|
||||
xaxis: {title: '日期', gridcolor: '#f1f3f4'},
|
||||
yaxis: {title: '注册人数', gridcolor: '#f1f3f4'},
|
||||
plot_bgcolor: 'rgba(0,0,0,0)',
|
||||
paper_bgcolor: 'rgba(0,0,0,0)',
|
||||
height: 350,
|
||||
margin: {l: 60, r: 40, t: 60, b: 60}
|
||||
};
|
||||
|
||||
Plotly.newPlot('userTrendChart', userTrendData, userTrendLayout);
|
||||
|
||||
// 用户状态分布图
|
||||
var userStatusData = [{
|
||||
values: [{{ stats.active_users }}, {{ stats.total_users - stats.active_users }}],
|
||||
values: [{{ stats.active_users if stats else 0 }}, {{ (stats.total_users - stats.active_users) if stats else 0 }}],
|
||||
labels: ['活跃用户', '禁用用户'],
|
||||
type: 'pie',
|
||||
marker: {colors: ['#28a745', '#dc3545']},
|
||||
textinfo: 'label+percent'
|
||||
textinfo: 'label+percent',
|
||||
hole: 0.4
|
||||
}];
|
||||
|
||||
var userStatusLayout = {
|
||||
title: '用户状态分布',
|
||||
height: 300,
|
||||
margin: {l: 50, r: 50, t: 50, b: 50}
|
||||
title: {text: '用户状态分布', font: {size: 16}},
|
||||
height: 350,
|
||||
margin: {l: 40, r: 40, t: 60, b: 40}
|
||||
};
|
||||
|
||||
Plotly.newPlot('userStatusChart', userStatusData, userStatusLayout);
|
||||
});
|
||||
|
||||
// 提示词生成趋势图
|
||||
const promptTrendData = [
|
||||
{date: '2025-08-23', count: 45},
|
||||
{date: '2025-08-24', count: 52},
|
||||
{date: '2025-08-25', count: 48},
|
||||
{date: '2025-08-26', count: 61},
|
||||
{date: '2025-08-27', count: 55},
|
||||
{date: '2025-08-28', count: 67},
|
||||
{date: '2025-08-29', count: 73}
|
||||
];
|
||||
|
||||
const promptDates = promptTrendData.map(item => item.date);
|
||||
const promptCounts = promptTrendData.map(item => item.count);
|
||||
|
||||
var promptTrendTrace = {
|
||||
x: promptDates,
|
||||
y: promptCounts,
|
||||
type: 'bar',
|
||||
name: '提示词数量',
|
||||
marker: {color: '#1cc88a'}
|
||||
};
|
||||
|
||||
var promptTrendLayout = {
|
||||
title: {text: '提示词生成趋势', font: {size: 16}},
|
||||
xaxis: {title: '日期', gridcolor: '#f1f3f4'},
|
||||
yaxis: {title: '生成数量', gridcolor: '#f1f3f4'},
|
||||
plot_bgcolor: 'rgba(0,0,0,0)',
|
||||
paper_bgcolor: 'rgba(0,0,0,0)',
|
||||
height: 300,
|
||||
margin: {l: 60, r: 40, t: 60, b: 60}
|
||||
};
|
||||
|
||||
Plotly.newPlot('promptTrendChart', [promptTrendTrace], promptTrendLayout);
|
||||
|
||||
// 用户活跃度图
|
||||
const activityData = [
|
||||
{hour: '00:00', users: 12},
|
||||
{hour: '04:00', users: 8},
|
||||
{hour: '08:00', users: 45},
|
||||
{hour: '12:00', users: 78},
|
||||
{hour: '16:00', users: 92},
|
||||
{hour: '20:00', users: 65},
|
||||
{hour: '24:00', users: 15}
|
||||
];
|
||||
|
||||
const hours = activityData.map(item => item.hour);
|
||||
const users = activityData.map(item => item.users);
|
||||
|
||||
var activityTrace = {
|
||||
x: hours,
|
||||
y: users,
|
||||
type: 'scatter',
|
||||
mode: 'lines+markers',
|
||||
name: '活跃用户',
|
||||
line: {color: '#36b9cc', width: 3},
|
||||
marker: {size: 8, color: '#36b9cc'},
|
||||
fill: 'tonexty',
|
||||
fillcolor: 'rgba(54, 185, 204, 0.1)'
|
||||
};
|
||||
|
||||
var activityLayout = {
|
||||
title: {text: '24小时用户活跃度', font: {size: 16}},
|
||||
xaxis: {title: '时间', gridcolor: '#f1f3f4'},
|
||||
yaxis: {title: '活跃用户数', gridcolor: '#f1f3f4'},
|
||||
plot_bgcolor: 'rgba(0,0,0,0)',
|
||||
paper_bgcolor: 'rgba(0,0,0,0)',
|
||||
height: 300,
|
||||
margin: {l: 60, r: 40, t: 60, b: 60}
|
||||
};
|
||||
|
||||
Plotly.newPlot('userActivityChart', [activityTrace], activityLayout);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,103 +2,269 @@
|
||||
|
||||
{% block title %}管理后台首页{% endblock %}
|
||||
|
||||
{% block page_title %}仪表板{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1 class="mb-4">
|
||||
<i class="fas fa-tachometer-alt"></i> 系统概览
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速操作 -->
|
||||
<!-- 欢迎横幅 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-bolt"></i> 快速操作
|
||||
<div class="stats-card">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<h2 class="mb-2">欢迎回来,{{ current_user.username }}!</h2>
|
||||
<p class="mb-0 opacity-75">今天是 {{ moment().format('YYYY年MM月DD日 dddd') if moment else '2025年8月29日 星期四' }},祝您工作愉快!</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<i class="fas fa-sun stats-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-users text-primary" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-primary">{{ stats.total_users if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">总用户数</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-primary" style="width: 75%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-magic text-success" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-success">{{ stats.total_prompts if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">总提示词</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-success" style="width: 85%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-calendar text-info" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-info">{{ stats.today_prompts if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">今日提示词</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-info" style="width: 60%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="stats-icon mb-3">
|
||||
<i class="fas fa-clipboard-list text-warning" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<h3 class="stats-number text-warning">{{ stats.total_templates if stats else 0 }}</h3>
|
||||
<p class="stats-label text-muted">模板数量</p>
|
||||
<div class="progress mt-2" style="height: 6px;">
|
||||
<div class="progress-bar bg-warning" style="width: 45%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能模块 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-th-large me-2"></i> 功能模块
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('admin_user.index_view') }}" class="btn btn-primary btn-block">
|
||||
<i class="fas fa-users"></i> 用户管理
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-users fa-3x text-primary"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">用户管理</h5>
|
||||
<p class="feature-desc text-muted">管理系统用户和权限</p>
|
||||
<a href="{{ url_for('admin_user.index_view') }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('admin_prompt.index_view') }}" class="btn btn-success btn-block">
|
||||
<i class="fas fa-magic"></i> 提示词管理
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-magic fa-3x text-success"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">提示词管理</h5>
|
||||
<p class="feature-desc text-muted">管理AI提示词内容</p>
|
||||
<a href="{{ url_for('admin_prompt.index_view') }}" class="btn btn-outline-success btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('admin_template.index_view') }}" class="btn btn-info btn-block">
|
||||
<i class="fas fa-clipboard-list"></i> 模板管理
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-chart-line fa-3x text-info"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">数据分析</h5>
|
||||
<p class="feature-desc text-muted">数据统计和可视化</p>
|
||||
<a href="{{ url_for('analytics_admin.index') }}" class="btn btn-outline-info btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="{{ url_for('admin_system.index') }}" class="btn btn-warning btn-block">
|
||||
<i class="fas fa-cogs"></i> 系统管理
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-database fa-3x text-warning"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">数据备份</h5>
|
||||
<p class="feature-desc text-muted">系统数据备份恢复</p>
|
||||
<a href="{{ url_for('backup_admin.index') }}" class="btn btn-outline-warning btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-tasks fa-3x text-danger"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">批量操作</h5>
|
||||
<p class="feature-desc text-muted">批量数据处理</p>
|
||||
<a href="{{ url_for('batch_admin.index') }}" class="btn btn-outline-danger btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-tachometer-alt fa-3x text-secondary"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">系统监控</h5>
|
||||
<p class="feature-desc text-muted">实时系统监控</p>
|
||||
<a href="{{ url_for('monitor_admin.index') }}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-chart-bar fa-3x text-dark"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">高级报表</h5>
|
||||
<p class="feature-desc text-muted">生成详细报表</p>
|
||||
<a href="{{ url_for('report_admin.index') }}" class="btn btn-outline-dark btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-3 col-md-4 col-sm-6 mb-4">
|
||||
<div class="feature-card text-center p-4">
|
||||
<div class="feature-icon mb-3">
|
||||
<i class="fas fa-code fa-3x text-primary"></i>
|
||||
</div>
|
||||
<h5 class="feature-title">API管理</h5>
|
||||
<p class="feature-desc text-muted">API调用管理</p>
|
||||
<a href="{{ url_for('api_admin.index') }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> 进入
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统信息 -->
|
||||
<!-- 系统状态 -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-info-circle"></i> 系统信息
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-info-circle me-2"></i> 系统信息
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<td><strong>系统名称:</strong></td>
|
||||
<td>提示词大师管理系统</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>当前用户:</strong></td>
|
||||
<td>{{ current_user.username if current_user.is_authenticated else '未登录' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>用户角色:</strong></td>
|
||||
<td>{{ current_user.role if current_user.is_authenticated else '无' }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="col-6">
|
||||
<p class="mb-2"><strong>系统版本:</strong> v1.0.0</p>
|
||||
<p class="mb-2"><strong>运行环境:</strong> Python 3.12.7</p>
|
||||
<p class="mb-2"><strong>数据库:</strong> MySQL</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<td><strong>最后登录:</strong></td>
|
||||
<td>{{ current_user.last_login.strftime('%Y-%m-%d %H:%M:%S') if current_user.is_authenticated and current_user.last_login else '首次登录' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>登录时间:</strong></td>
|
||||
<td>{{ current_user.created_at.strftime('%Y-%m-%d %H:%M:%S') if current_user.is_authenticated else '无' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>账户状态:</strong></td>
|
||||
<td>
|
||||
{% if current_user.is_authenticated %}
|
||||
<span class="badge badge-{{ 'success' if current_user.is_active else 'danger' }}">
|
||||
{{ '正常' if current_user.is_active else '禁用' }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge badge-secondary">未登录</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="col-6">
|
||||
<p class="mb-2"><strong>最后更新:</strong> 2025-08-29</p>
|
||||
<p class="mb-2"><strong>在线用户:</strong> {{ stats.total_users if stats else 0 }}</p>
|
||||
<p class="mb-2"><strong>系统状态:</strong> <span class="badge bg-success">正常</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold">
|
||||
<i class="fas fa-clock me-2"></i> 最近活动
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="activity-item d-flex align-items-center mb-3">
|
||||
<div class="activity-icon bg-primary text-white rounded-circle p-2 me-3">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-0"><strong>新用户注册</strong></p>
|
||||
<small class="text-muted">2分钟前</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-item d-flex align-items-center mb-3">
|
||||
<div class="activity-icon bg-success text-white rounded-circle p-2 me-3">
|
||||
<i class="fas fa-magic"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-0"><strong>生成提示词</strong></p>
|
||||
<small class="text-muted">5分钟前</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="activity-item d-flex align-items-center">
|
||||
<div class="activity-icon bg-info text-white rounded-circle p-2 me-3">
|
||||
<i class="fas fa-database"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-0"><strong>数据备份</strong></p>
|
||||
<small class="text-muted">10分钟前</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,9 +274,93 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.btn-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
.feature-card {
|
||||
border: 1px solid #e3e6f0;
|
||||
border-radius: 0.75rem;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.1);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover .feature-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.activity-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stats-number {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.progress {
|
||||
border-radius: 1rem;
|
||||
background-color: #f8f9fc;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 添加动态效果
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 数字动画
|
||||
const statsNumbers = document.querySelectorAll('.stats-number');
|
||||
statsNumbers.forEach(number => {
|
||||
const finalValue = parseInt(number.textContent);
|
||||
animateNumber(number, 0, finalValue, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
function animateNumber(element, start, end, duration) {
|
||||
const startTime = performance.now();
|
||||
|
||||
function updateNumber(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
const current = Math.floor(start + (end - start) * progress);
|
||||
element.textContent = current;
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(updateNumber);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(updateNumber);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show" role="alert">
|
||||
<i class="fas fa-{{ 'exclamation-triangle' if category == 'error' else 'check-circle' }} me-2"></i>
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
@@ -133,5 +134,29 @@
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script>
|
||||
// 页面加载动画
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const loginCard = document.querySelector('.login-card');
|
||||
loginCard.style.opacity = '0';
|
||||
loginCard.style.transform = 'translateY(30px)';
|
||||
|
||||
setTimeout(() => {
|
||||
loginCard.style.transition = 'all 0.6s ease-out';
|
||||
loginCard.style.opacity = '1';
|
||||
loginCard.style.transform = 'translateY(0)';
|
||||
}, 100);
|
||||
|
||||
// 表单提交动画
|
||||
const form = document.querySelector('form');
|
||||
const submitBtn = document.querySelector('.btn-login');
|
||||
|
||||
form.addEventListener('submit', function() {
|
||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 登录中...';
|
||||
submitBtn.disabled = true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
69
src/flask_prompt_master/templates/admin/logout_confirm.html
Normal file
69
src/flask_prompt_master/templates/admin/logout_confirm.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends 'admin/master.html' %}
|
||||
|
||||
{% block title %}确认登出{% endblock %}
|
||||
|
||||
{% block page_title %}确认登出{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-sign-out-alt text-warning me-2"></i>
|
||||
确认登出
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-4">
|
||||
<i class="fas fa-question-circle text-warning" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
<h6 class="mb-3">您确定要登出后台管理系统吗?</h6>
|
||||
<p class="text-muted mb-4">
|
||||
当前用户:<strong>{{ current_user.username }}</strong><br>
|
||||
登出后将返回登录页面
|
||||
</p>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{{ url_for('admin.logout') }}" class="btn btn-danger">
|
||||
<i class="fas fa-sign-out-alt me-2"></i>
|
||||
确认登出
|
||||
</a>
|
||||
<a href="{{ url_for('admin.index') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-2"></i>
|
||||
取消
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
border-radius: 1rem 1rem 0 0 !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
padding: 0.75rem 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
590
src/flask_prompt_master/templates/admin/master.html
Normal file
590
src/flask_prompt_master/templates/admin/master.html
Normal file
@@ -0,0 +1,590 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>{% block title %}{% endblock %} - 提示词大师后台管理</title>
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<!-- Custom CSS -->
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #4e73df;
|
||||
--secondary-color: #858796;
|
||||
--success-color: #1cc88a;
|
||||
--info-color: #36b9cc;
|
||||
--warning-color: #f6c23e;
|
||||
--danger-color: #e74a3b;
|
||||
--dark-color: #5a5c69;
|
||||
--light-color: #f8f9fc;
|
||||
--sidebar-width: 250px;
|
||||
--header-height: 70px;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 侧边栏样式 */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--sidebar-width);
|
||||
height: 100vh;
|
||||
background: linear-gradient(180deg, #2c3e50 0%, #34495e 100%);
|
||||
box-shadow: 4px 0 10px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-brand:hover {
|
||||
color: #4e73df;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
margin: 0.25rem 1rem;
|
||||
}
|
||||
|
||||
.sidebar-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
color: rgba(255,255,255,0.8);
|
||||
text-decoration: none;
|
||||
border-radius: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-link:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.sidebar-link.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
box-shadow: 0 4px 8px rgba(78, 115, 223, 0.3);
|
||||
}
|
||||
|
||||
.sidebar-icon {
|
||||
width: 20px;
|
||||
margin-right: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 主内容区域 */
|
||||
.main-content {
|
||||
margin-left: var(--sidebar-width);
|
||||
min-height: 100vh;
|
||||
background: #f8f9fc;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.top-navbar {
|
||||
background: white;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--dark-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.user-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-weight: 600;
|
||||
color: var(--dark-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
font-size: 0.875rem;
|
||||
color: var(--secondary-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
background: linear-gradient(135deg, var(--danger-color) 0%, #dc3545 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 2px 8px rgba(231, 74, 59, 0.3);
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background: linear-gradient(135deg, #c82333 0%, #bd2130 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(231, 74, 59, 0.4);
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content-wrapper {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* 卡片样式优化 */
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
|
||||
transition: all 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 30px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #6f42c1 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.card-header h6 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
/* 按钮样式优化 */
|
||||
.btn {
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #6f42c1 100%);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, var(--success-color) 0%, #20c997 100%);
|
||||
}
|
||||
|
||||
.btn-info {
|
||||
background: linear-gradient(135deg, var(--info-color) 0%, #17a2b8 100%);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: linear-gradient(135deg, var(--warning-color) 0%, #fd7e14 100%);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: linear-gradient(135deg, var(--danger-color) 0%, #dc3545 100%);
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
.table {
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
color: var(--dark-color);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.table tbody td {
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background: rgba(78, 115, 223, 0.05);
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.stats-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stats-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-radius: 50%;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.stats-card .stats-icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 3rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.stats-card .stats-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.stats-card .stats-number {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stats-card .stats-label {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.9;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in-up {
|
||||
animation: fadeInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
.loading-spinner {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(255,255,255,.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #fff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 通知样式 */
|
||||
.alert {
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: linear-gradient(135deg, #d1ecf1 0%, #bee5eb 100%);
|
||||
color: #0c5460;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<!-- 侧边栏 -->
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<a href="{{ url_for('admin.index') }}" class="sidebar-brand">
|
||||
<i class="fas fa-magic me-2"></i>
|
||||
提示词大师
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-menu">
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('admin.index') }}" class="sidebar-link {% if request.endpoint == 'admin.index' %}active{% endif %}">
|
||||
<i class="fas fa-tachometer-alt sidebar-icon"></i>
|
||||
仪表板
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('admin_user.index_view') }}" class="sidebar-link {% if 'admin_user' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-users sidebar-icon"></i>
|
||||
用户管理
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('admin_prompt.index_view') }}" class="sidebar-link {% if 'admin_prompt' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-magic sidebar-icon"></i>
|
||||
提示词管理
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('admin_template.index_view') }}" class="sidebar-link {% if 'admin_template' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-clipboard-list sidebar-icon"></i>
|
||||
模板管理
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('analytics_admin.index') }}" class="sidebar-link {% if 'analytics_admin' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-chart-line sidebar-icon"></i>
|
||||
数据分析
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('batch_admin.index') }}" class="sidebar-link {% if 'batch_admin' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-tasks sidebar-icon"></i>
|
||||
批量操作
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('monitor_admin.index') }}" class="sidebar-link {% if 'monitor_admin' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-tachometer-alt sidebar-icon"></i>
|
||||
系统监控
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('report_admin.index') }}" class="sidebar-link {% if 'report_admin' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-chart-bar sidebar-icon"></i>
|
||||
高级报表
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('backup_admin.index') }}" class="sidebar-link {% if 'backup_admin' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-database sidebar-icon"></i>
|
||||
数据备份
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('api_admin.index') }}" class="sidebar-link {% if 'api_admin' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-code sidebar-icon"></i>
|
||||
API管理
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-item">
|
||||
<a href="{{ url_for('admin_system.index') }}" class="sidebar-link {% if 'admin_system' in request.endpoint %}active{% endif %}">
|
||||
<i class="fas fa-cogs sidebar-icon"></i>
|
||||
系统管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="main-content">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="top-navbar">
|
||||
<h1 class="page-title">
|
||||
{% block page_title %}{% endblock %}
|
||||
</h1>
|
||||
|
||||
<div class="user-menu">
|
||||
<div class="user-info">
|
||||
<p class="user-name">{{ current_user.username if current_user.is_authenticated else '游客' }}</p>
|
||||
<p class="user-role">{{ current_user.role if current_user.is_authenticated else '未登录' }}</p>
|
||||
</div>
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('admin.logout_confirm') }}" class="logout-btn">
|
||||
<i class="fas fa-sign-out-alt me-2"></i>
|
||||
退出登录
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 内容包装器 -->
|
||||
<div class="content-wrapper">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ 'danger' if category == 'error' else category }} alert-dismissible fade show" role="alert">
|
||||
<i class="fas fa-{{ 'exclamation-triangle' if category == 'error' else 'info-circle' }} me-2"></i>
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
|
||||
<script>
|
||||
// 页面加载动画
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const contentElements = document.querySelectorAll('.card, .stats-card');
|
||||
contentElements.forEach((element, index) => {
|
||||
element.style.opacity = '0';
|
||||
element.style.transform = 'translateY(30px)';
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.transition = 'all 0.6s ease-out';
|
||||
element.style.opacity = '1';
|
||||
element.style.transform = 'translateY(0)';
|
||||
}, index * 100);
|
||||
});
|
||||
});
|
||||
|
||||
// 侧边栏响应式处理
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
sidebar.classList.toggle('show');
|
||||
}
|
||||
|
||||
// 添加移动端菜单按钮
|
||||
if (window.innerWidth <= 768) {
|
||||
const topNavbar = document.querySelector('.top-navbar');
|
||||
const menuButton = document.createElement('button');
|
||||
menuButton.className = 'btn btn-primary d-md-none';
|
||||
menuButton.innerHTML = '<i class="fas fa-bars"></i>';
|
||||
menuButton.onclick = toggleSidebar;
|
||||
topNavbar.insertBefore(menuButton, topNavbar.firstChild);
|
||||
}
|
||||
|
||||
// 自动隐藏通知
|
||||
setTimeout(() => {
|
||||
const alerts = document.querySelectorAll('.alert');
|
||||
alerts.forEach(alert => {
|
||||
const bsAlert = new bootstrap.Alert(alert);
|
||||
bsAlert.close();
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
// 登出确认功能
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const logoutBtn = document.querySelector('.logout-btn');
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener('click', function(e) {
|
||||
// 这里可以添加额外的确认逻辑
|
||||
// 目前使用页面确认,所以不需要阻止默认行为
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user