mirror of
https://github.com/snailyp/gemini-balance.git
synced 2026-05-12 02:19:59 +08:00
- 将 `get_current_version` 函数从 `application.py` 移动到 `helpers.py` 以实现更好的代码组织和可重用性。 - 在 `version_routes.py` 中引入新的 API 端点 `/api/version/check`,以提供当前版本、最新可用版本和更新状态。 - 更新了 `base.html`,通过调用新的 API 端点,使用 JavaScript 异步获取和显示版本信息。这取代了以前服务器端渲染版本信息的方式,并增加了定期检查。 - 移除了应用程序启动时(`lifespan` 函数)的自动更新检查,因为版本检查现在由前端通过 API 处理。 - 在 `routes.py` 中注册了新的版本路由。
316 lines
13 KiB
HTML
316 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 %}Gemini Balance{% endblock %}</title>
|
|
<link rel="manifest" href="/static/manifest.json">
|
|
<meta name="theme-color" content="#4F46E5">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
|
<meta name="apple-mobile-web-app-title" content="GBalance">
|
|
<link rel="icon" href="/static/icons/icon-192x192.png">
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
primary: {
|
|
50: '#eef2ff',
|
|
100: '#e0e7ff',
|
|
200: '#c7d2fe',
|
|
300: '#a5b4fc',
|
|
400: '#818cf8',
|
|
500: '#6366f1',
|
|
600: '#4f46e5',
|
|
700: '#4338ca',
|
|
800: '#3730a3',
|
|
900: '#312e81',
|
|
},
|
|
success: {
|
|
50: '#ecfdf5',
|
|
500: '#10b981',
|
|
600: '#059669'
|
|
},
|
|
danger: {
|
|
50: '#fef2f2',
|
|
500: '#ef4444',
|
|
600: '#dc2626'
|
|
}
|
|
},
|
|
fontFamily: {
|
|
sans: ['Inter', 'sans-serif'],
|
|
mono: ['JetBrains Mono', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
|
|
},
|
|
animation: {
|
|
'fade-in': 'fadeIn 0.5s ease-out',
|
|
'slide-up': 'slideUp 0.5s ease-out',
|
|
'slide-down': 'slideDown 0.5s ease-out',
|
|
'shake': 'shake 0.5s ease-in-out',
|
|
'spin': 'spin 1s linear infinite',
|
|
},
|
|
keyframes: {
|
|
fadeIn: {
|
|
'0%': { opacity: '0' },
|
|
'100%': { opacity: '1' },
|
|
},
|
|
slideUp: {
|
|
'0%': { transform: 'translateY(20px)', opacity: '0' },
|
|
'100%': { transform: 'translateY(0)', opacity: '1' },
|
|
},
|
|
slideDown: {
|
|
'0%': { transform: 'translateY(-20px)', opacity: '0' },
|
|
'100%': { transform: 'translateY(0)', opacity: '1' },
|
|
},
|
|
shake: {
|
|
'0%, 100%': { transform: 'translateX(0)' },
|
|
'25%': { transform: 'translateX(-5px)' },
|
|
'75%': { transform: 'translateX(5px)' },
|
|
},
|
|
spin: {
|
|
'0%': { transform: 'rotate(0deg)' },
|
|
'100%': { transform: 'rotate(360deg)' },
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style>
|
|
.glass-card {
|
|
background: rgba(255, 255, 255, 0.85); /* Slightly increased opacity for better readability */
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
border: 1px solid rgba(255, 255, 255, 0.18); /* Subtle border */
|
|
}
|
|
.bg-gradient {
|
|
background: linear-gradient(135deg, #4F46E5 0%, #7C3AED 50%, #EC4899 100%);
|
|
}
|
|
/* Scrollbar styling */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
::-webkit-scrollbar-track {
|
|
background: rgba(243, 244, 246, 0.8); /* bg-gray-100 with opacity */
|
|
border-radius: 10px;
|
|
}
|
|
::-webkit-scrollbar-thumb {
|
|
background: rgba(79, 70, 229, 0.4); /* primary-600 with opacity */
|
|
border-radius: 10px;
|
|
}
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(79, 70, 229, 0.6); /* primary-600 with more opacity */
|
|
}
|
|
/* Basic modal styles */
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 50;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0,0,0,0.5);
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
.modal.show {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
/* Loading spinner */
|
|
.loading-spin {
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
@keyframes spin {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
/* Notification */
|
|
.notification {
|
|
position: fixed;
|
|
bottom: 5rem; /* Adjusted from bottom-20 */
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
padding: 0.75rem 1.25rem; /* px-5 py-3 */
|
|
border-radius: 0.5rem; /* rounded-lg */
|
|
background-color: rgba(0, 0, 0, 0.8);
|
|
color: white;
|
|
font-weight: 500; /* font-medium */
|
|
z-index: 1000; /* Increased z-index */
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
|
|
}
|
|
.notification.show {
|
|
opacity: 1;
|
|
transform: translate(-50%, 0);
|
|
}
|
|
.notification.error {
|
|
background-color: rgba(220, 38, 38, 0.8); /* danger-600 with opacity */
|
|
}
|
|
/* Scroll buttons */
|
|
.scroll-buttons {
|
|
position: fixed;
|
|
right: 1.25rem; /* right-5 */
|
|
bottom: 5rem; /* bottom-20 */
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem; /* gap-2 */
|
|
z-index: 10;
|
|
}
|
|
.scroll-button {
|
|
width: 2.5rem; /* w-10 */
|
|
height: 2.5rem; /* h-10 */
|
|
background-color: #4f46e5; /* bg-primary-600 */
|
|
color: white;
|
|
border-radius: 9999px; /* rounded-full */
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-md */
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.3s ease-in-out;
|
|
}
|
|
.scroll-button:hover {
|
|
background-color: #4338ca; /* hover:bg-primary-700 */
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* hover:shadow-lg */
|
|
}
|
|
{% block head_extra_styles %}
|
|
{% endblock %}
|
|
</style>
|
|
{% block head_extra_scripts %}{% endblock %}
|
|
</head>
|
|
<body class="bg-gradient min-h-screen text-gray-800 pt-6 pb-16">
|
|
{% block content %}{% endblock %}
|
|
|
|
<!-- 底部版权 -->
|
|
<div class="fixed bottom-0 left-0 w-full py-3 bg-white bg-opacity-80 backdrop-blur-md text-center text-sm text-gray-600 border-t border-gray-200">
|
|
© <span id="copyright-year"></span> by
|
|
<a href="https://linux.do/u/snaily" target="_blank" class="text-primary-600 hover:text-primary-800 transition duration-300">
|
|
<img src="https://linux.do/user_avatar/linux.do/snaily/288/306510_2.gif" alt="snaily" class="inline-block w-5 h-5 rounded-full align-middle mr-1">snaily
|
|
</a> |
|
|
<a href="https://github.com/snailyp/gemini-balance" target="_blank" class="text-primary-600 hover:text-primary-800 transition duration-300">
|
|
<i class="fab fa-github"></i> GitHub
|
|
</a> |
|
|
<a href="https://afdian.com/a/snaily" target="_blank" class="text-primary-600 hover:text-primary-800 transition duration-300">
|
|
<i class="fas fa-drumstick-bite text-yellow-600"></i> 给作者加鸡腿
|
|
</a>
|
|
<span class="mx-1">|</span>
|
|
<span class="text-xs text-yellow-600 font-semibold">
|
|
<i class="fas fa-exclamation-triangle mr-1"></i>免费项目,谨防诈骗
|
|
</span>
|
|
<span id="version-info-container" class="inline-block">
|
|
<!-- Version info will be loaded here by JavaScript -->
|
|
</span>
|
|
</div>
|
|
|
|
<!-- 通用JS -->
|
|
<script>
|
|
// 设置版权年份
|
|
document.getElementById('copyright-year').textContent = new Date().getFullYear();
|
|
|
|
// 滚动到顶部/底部函数 (如果页面需要)
|
|
function scrollToTop() {
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
}
|
|
function scrollToBottom() {
|
|
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
|
}
|
|
|
|
// 显示通知
|
|
function showNotification(message, type = 'success', duration = 3000) {
|
|
const notification = document.getElementById('notification') || createNotificationElement();
|
|
if (!notification) return;
|
|
|
|
notification.textContent = message;
|
|
notification.className = 'notification show'; // Reset classes
|
|
if (type === 'error') {
|
|
notification.classList.add('error');
|
|
}
|
|
|
|
// Clear previous timeout if exists
|
|
if (notification.timeoutId) {
|
|
clearTimeout(notification.timeoutId);
|
|
}
|
|
|
|
notification.timeoutId = setTimeout(() => {
|
|
notification.classList.remove('show');
|
|
// Optional: remove the element after fade out if dynamically created
|
|
// setTimeout(() => notification.remove(), 300);
|
|
}, duration);
|
|
}
|
|
|
|
// Helper to create notification element if it doesn't exist
|
|
function createNotificationElement() {
|
|
let notification = document.getElementById('notification');
|
|
if (!notification) {
|
|
notification = document.createElement('div');
|
|
notification.id = 'notification';
|
|
notification.className = 'notification';
|
|
document.body.appendChild(notification);
|
|
}
|
|
return notification;
|
|
}
|
|
|
|
// 页面刷新带加载状态
|
|
function refreshPage(button) {
|
|
if (button) {
|
|
const icon = button.querySelector('i');
|
|
if (icon) {
|
|
icon.classList.add('loading-spin');
|
|
}
|
|
}
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 300); // Short delay to show spinner
|
|
}
|
|
|
|
// --- Version Check ---
|
|
const versionInfoContainer = document.getElementById('version-info-container');
|
|
|
|
async function fetchVersionInfo() {
|
|
if (!versionInfoContainer) return;
|
|
versionInfoContainer.innerHTML = '<span class="mx-1">|</span><span class="text-xs text-gray-400">检查更新中...</span>'; // Initial loading state
|
|
|
|
try {
|
|
const response = await fetch('/api/version/check');
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
const data = await response.json();
|
|
|
|
let versionHtml = `<span class="mx-1">|</span><span class="text-xs text-gray-500">v${data.current_version}</span>`;
|
|
if (data.update_available) {
|
|
versionHtml += `
|
|
<span class="mx-1">|</span>
|
|
<a href="https://github.com/snailyp/gemini-balance/releases/latest" target="_blank" class="text-yellow-600 hover:text-yellow-800 transition duration-300 animate-pulse">
|
|
<i class="fas fa-arrow-up"></i> 新版本: v${data.latest_version}
|
|
</a>`;
|
|
} else if (data.error_message) {
|
|
versionHtml += `
|
|
<span class="mx-1">|</span>
|
|
<span class="text-xs text-red-500" title="${data.error_message}">更新检查失败</span>`;
|
|
} else {
|
|
versionHtml += `<span class="mx-1">|</span><span class="text-xs text-green-500">已是最新</span>`; // Indicate up-to-date
|
|
}
|
|
versionInfoContainer.innerHTML = versionHtml;
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching version info:', error);
|
|
versionInfoContainer.innerHTML = `<span class="mx-1">|</span><span class="text-xs text-red-500" title="无法连接到服务器或解析响应">更新检查失败</span>`;
|
|
}
|
|
}
|
|
|
|
// Fetch immediately on load
|
|
fetchVersionInfo();
|
|
|
|
// Fetch periodically (e.g., every hour)
|
|
setInterval(fetchVersionInfo, 3600000); // 3600000 ms = 1 hour
|
|
|
|
</script>
|
|
{% block body_scripts %}{% endblock %}
|
|
</body>
|
|
</html> |