Kamixitong/app/web/templates/base.html
2025-11-13 16:51:51 +08:00

293 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>{% 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">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Custom CSS -->
<link href="/static/css/custom.css" rel="stylesheet">
<style>
.sidebar {
min-height: 100vh;
background-color: #343a40;
}
.sidebar .nav-link {
color: #fff;
padding: 1rem;
border-radius: 0;
}
.sidebar .nav-link:hover,
.sidebar .nav-link.active {
background-color: #495057;
color: #fff;
}
.main-content {
min-height: 100vh;
background-color: #f8f9fa;
}
.card-stats {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.loading {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
}
.spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<!-- Loading Spinner -->
<div class="loading" id="loading">
<div class="spinner">
<div class="spinner-border text-light" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</div>
</div>
{% if current_user.is_authenticated %}
<!-- Main Container -->
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
<div class="position-sticky pt-3">
<div class="text-center mb-4">
<h5 class="text-white">太一软件管理系统</h5>
<small class="text-muted">欢迎, {{ current_user.username }}</small>
</div>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint == 'web.dashboard' }}" href="{{ url_for('web.dashboard') }}">
<i class="fas fa-tachometer-alt me-2"></i>
仪表板
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'product' in request.endpoint }}" href="{{ url_for('web.products') }}">
<i class="fas fa-box me-2"></i>
产品管理
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'license' in request.endpoint }}" href="{{ url_for('web.licenses') }}">
<i class="fas fa-key me-2"></i>
卡密管理
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'version' in request.endpoint }}" href="{{ url_for('web.versions') }}">
<i class="fas fa-code-branch me-2"></i>
版本管理
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'device' in request.endpoint }}" href="{{ url_for('web.devices') }}">
<i class="fas fa-desktop me-2"></i>
设备管理
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'ticket' in request.endpoint }}" href="{{ url_for('web.tickets') }}">
<i class="fas fa-ticket-alt me-2"></i>
工单管理
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'statistics' in request.endpoint }}" href="{{ url_for('web.statistics') }}">
<i class="fas fa-chart-bar me-2"></i>
统计分析
</a>
</li>
{% if current_user.is_super_admin() %}
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'admin' in request.endpoint }}" href="{{ url_for('web.admins') }}">
<i class="fas fa-user-cog me-2"></i>
账号管理
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.endpoint and 'settings' in request.endpoint }}" href="{{ url_for('web.settings') }}">
<i class="fas fa-cog me-2"></i>
系统设置
</a>
</li>
{% endif %}
</ul>
<hr class="text-white">
<div class="text-center">
<a href="{{ url_for('web.logout') }}" class="btn btn-outline-light btn-sm">
<i class="fas fa-sign-out-alt me-1"></i>
退出登录
</a>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{% block page_title %}仪表板{% endblock %}</h1>
<div class="btn-toolbar mb-2 mb-md-0">
{% block page_actions %}{% endblock %}
</div>
</div>
<!-- Flash Messages -->
{% 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">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<!-- Page Content -->
{% block content %}{% endblock %}
</main>
</div>
</div>
{% else %}
<!-- Login Layout -->
<div class="container-fluid vh-100 d-flex align-items-center justify-content-center bg-light">
<div class="card shadow-lg" style="width: 100%; max-width: 400px;">
<div class="card-body">
{% block login_content %}{% endblock %}
</div>
</div>
</div>
{% endif %}
<!-- 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>
<!-- Custom JS -->
<script src="/static/js/custom.js"></script>
<!-- Common Functions -->
<script>
// 显示加载动画
function showLoading() {
document.getElementById('loading').style.display = 'block';
}
// 隐藏加载动画
function hideLoading() {
document.getElementById('loading').style.display = 'none';
}
// AJAX请求封装
function apiRequest(url, options = {}) {
showLoading();
const defaultOptions = {
headers: {
'Content-Type': 'application/json'
},
credentials: 'same-origin' // 重要: 使用session cookies
};
return fetch(url, { ...defaultOptions, ...options })
.then(response => {
hideLoading();
if (!response.ok) {
// 对于401错误表示需要重新登录
if (response.status === 401) {
showNotification('会话已过期,请重新登录', 'warning');
// 延迟重定向,给用户看到提示的时间
setTimeout(() => {
window.location.href = '/login';
}, 1500);
}
// 返回错误信息而不是直接抛出异常
return response.json().then(errorData => {
throw new Error(`${response.status}: ${errorData.message || response.statusText}`);
}).catch(() => {
// 如果无法解析JSON错误信息则使用默认消息
throw new Error(`${response.status}: ${response.statusText}`);
});
}
return response.json();
})
.catch(error => {
hideLoading();
console.error('API request failed:', error);
throw error;
});
}
// 格式化日期
function formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN');
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 显示通知
function showNotification(message, type = 'info') {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
const container = document.querySelector('main');
if (container) {
container.insertBefore(alertDiv, container.firstChild);
// 自动隐藏
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
}
// 页面加载完成后隐藏加载动画
document.addEventListener('DOMContentLoaded', function() {
hideLoading();
});
</script>
{% block extra_js %}{% endblock %}
</body>
</html>