pre / templates /base.html
yangtb24's picture
Upload 64 files
834bfcd verified
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API密钥管理系统</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 自定义Tailwind配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e',
950: '#082f49',
},
},
animation: {
'fade-in': 'fadeIn 0.3s ease-in-out',
'fade-out': 'fadeOut 0.3s ease-in-out',
'slide-down': 'slideDown 0.3s ease-in-out',
'slide-up': 'slideUp 0.3s ease-in-out',
'expand': 'expand 0.3s ease-in-out',
'collapse': 'collapse 0.3s ease-in-out',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
fadeOut: {
'0%': { opacity: '1' },
'100%': { opacity: '0' },
},
slideDown: {
'0%': { maxHeight: '0', opacity: '0', transform: 'translateY(-10px)' },
'100%': { maxHeight: '1000px', opacity: '1', transform: 'translateY(0)' }
},
slideUp: {
'0%': { maxHeight: '1000px', opacity: '1', transform: 'translateY(0)' },
'100%': { maxHeight: '0', opacity: '0', transform: 'translateY(-10px)' }
},
expand: {
'0%': { transform: 'scaleY(0)', transformOrigin: 'top', opacity: '0' },
'100%': { transform: 'scaleY(1)', transformOrigin: 'top', opacity: '1' }
},
collapse: {
'0%': { transform: 'scaleY(1)', transformOrigin: 'top', opacity: '1' },
'100%': { transform: 'scaleY(0)', transformOrigin: 'top', opacity: '0' }
},
},
},
},
}
</script>
<!-- Alpine.js -->
<script defer src="https://unpkg.com/alpinejs@3.14.8/dist/cdn.min.js"></script>
<!-- Clipboard.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
<!-- SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- 自定义样式 -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="icon" href="{{ url_for('static', filename='img/favicon.ico') }}">
{% block head %}{% endblock %}
</head>
<body class="bg-gray-50 text-gray-900 min-h-screen" x-data="apiKeyManager()">
<!-- 顶部导航 -->
<header class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<!-- 三栏布局:标题、选项卡(居中)和退出按钮 -->
<div class="grid grid-cols-3 items-center">
<!-- 左侧 - 标题 -->
<div class="flex items-center justify-start">
<h1 class="text-2xl font-bold text-primary-700">
API密钥管理系统
</h1>
</div>
<!-- 中间 - 选项卡居中 -->
<div class="flex justify-center">
{% block header_tabs %}{% endblock %}
</div>
<!-- 右侧 - 退出按钮 -->
<div class="flex items-center justify-end">
<a href="{{ url_for('web.logout') }}" class="flex items-center px-3 py-2 text-sm font-medium text-primary-700 hover:text-primary-900 hover:bg-primary-50 rounded-md transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
退出登录
</a>
</div>
</div>
</div>
</header>
<!-- 主内容区 -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{% block content %}{% endblock %}
</main>
<!-- 回到顶部按钮 -->
<div id="backToTop" class="back-to-top" title="回到顶部">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
</svg>
</div>
<!-- 通知弹出组件 -->
<div x-data="notificationSystem()"
x-init="init()"
@show-notification.window="add($event.detail.message, $event.detail.type)"
class="fixed bottom-4 right-4 z-50 space-y-4"
x-cloak>
<template x-for="notification in notifications" :key="notification.id">
<div x-show="notification.visible"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="transform translate-y-2 opacity-0"
x-transition:enter-end="transform translate-y-0 opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="transform translate-y-0 opacity-100"
x-transition:leave-end="transform translate-y-2 opacity-0"
:class="{
'bg-green-50 text-green-800 border-green-400': notification.type === 'success',
'bg-red-50 text-red-800 border-red-400': notification.type === 'error',
'bg-blue-50 text-blue-800 border-blue-400': notification.type === 'info'
}"
class="p-4 border-l-4 shadow-md rounded-r-lg flex justify-between items-center min-w-[300px]">
<div x-text="notification.message"></div>
<button @click="remove(notification.id)" class="ml-4 text-gray-500 hover:text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</div>
</template>
</div>
<!-- 自定义脚本 -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% block scripts %}{% endblock %}
<script>
// 通知系统
function notificationSystem() {
return {
notifications: [],
nextId: 1,
init() {
// 初始化通知系统
},
add(message, type = 'info') {
const id = this.nextId++;
const notification = {
id,
message,
type,
visible: true
};
this.notifications.push(notification);
// 3秒后自动移除
setTimeout(() => {
this.remove(id);
}, 3000);
},
remove(id) {
const index = this.notifications.findIndex(n => n.id === id);
if (index !== -1) {
// 隐藏通知
this.notifications[index].visible = false;
// 300毫秒后从数组中移除(与过渡动画时间一致)
setTimeout(() => {
this.notifications = this.notifications.filter(n => n.id !== id);
}, 300);
}
}
};
}
</script>
</body>
</html>