311 lines
13 KiB
HTML
311 lines
13 KiB
HTML
<!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>
|
||
<li class="nav-item">
|
||
<a class="nav-link {{ 'active' if request.endpoint and 'log' in request.endpoint }}" href="{{ url_for('web.logs') }}">
|
||
<i class="fas fa-file-alt 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);
|
||
}
|
||
// 对于403错误,显示权限不足的提示
|
||
else if (response.status === 403) {
|
||
// 先尝试解析错误信息
|
||
return response.json().then(errorData => {
|
||
showNotification(errorData.message || '权限不足,无法执行此操作', 'error');
|
||
throw new Error(`403: ${errorData.message || '权限不足'}`);
|
||
}).catch(() => {
|
||
// 如果无法解析JSON,使用默认消息
|
||
showNotification('权限不足,无法执行此操作', 'error');
|
||
throw new Error('403: 权限不足');
|
||
});
|
||
}
|
||
// 其他错误状态码
|
||
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> |