Files
one-api-workers/public/js/utils.js
T
dreamhunter2333andClaude Sonnet 4.5 198dc500aa feat: 重构管理界面并添加统一 loading 效果
- 完全重构前端管理界面,采用模块化架构
- 新增独立的 CSS 和 JS 模块(utils, auth, channels, tokens, pricing, api-test, database)
- 实现统一的 loading 遮罩效果,所有 API 请求自动显示加载状态
- 添加现代化多选下拉框组件用于频道选择
- 实现模型部署映射的可视化表格编辑
- 修复页面高度自适应问题,管理页面占满全屏
- 优化侧边栏布局,添加 GitHub 链接和版本号
- 令牌管理页面隐藏敏感 key,添加复制功能
- 管理页面切换时自动重置为列表视图并重新加载数据
- 新增 Claude Proxy 提供商支持
- 统一所有 API 调用的错误处理和 loading 状态管理

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-17 18:41:05 +08:00

181 lines
5.4 KiB
JavaScript
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.
// ===== Utility Functions =====
// Modal Management
export function toggleModal(modalId) {
const modal = document.getElementById(modalId);
modal.classList.toggle('active');
}
// Tab Switching
export function switchTab(tabName) {
// Update active tab content
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
document.getElementById(tabName).classList.add('active');
// Update active nav item
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
const targetNav = document.querySelector(`[data-tab="${tabName}"]`);
if (targetNav) {
targetNav.classList.add('active');
}
// Update page title
const titles = {
'dashboard': '仪表盘',
'api-test': 'API 测试',
'channels': '频道管理',
'tokens': '令牌管理',
'pricing': '定价管理',
'database': '数据库管理'
};
document.getElementById('pageTitle').textContent = titles[tabName] || tabName;
// Load data for specific tabs if authenticated
const adminToken = localStorage.getItem('adminToken');
if (adminToken) {
// Reset to list view and reload data for management pages
if (tabName === 'channels') {
window.showChannelList && window.showChannelList();
}
if (tabName === 'tokens') {
window.showTokenList && window.showTokenList();
}
if (tabName === 'pricing') {
window.loadPricing && window.loadPricing();
}
}
}
// Theme Toggle
export function toggleTheme() {
const isDark = document.documentElement.classList.contains('dark');
if (isDark) {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
document.getElementById('themeIcon').textContent = '🌙';
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
document.getElementById('themeIcon').textContent = '☀️';
}
}
// Notification System
export function showNotification(message, type = 'info') {
const className = type === 'success' ? 'alert-success' : (type === 'error' ? 'alert-error' : 'alert-info');
console.log(`[${type.toUpperCase()}] ${message}`);
// Create toast notification
const toast = document.createElement('div');
toast.className = `alert ${className}`;
toast.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 100; min-width: 300px; animation: slideIn 0.3s ease;';
toast.innerHTML = `<strong>${type === 'success' ? '✅' : type === 'error' ? '❌' : 'ℹ️'}</strong> <span>${message}</span>`;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'fadeOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Copy to Clipboard
export function copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
showNotification('已复制到剪贴板', 'success');
}).catch(err => {
console.error('复制失败:', err);
fallbackCopy(text);
});
} else {
fallbackCopy(text);
}
}
// Fallback copy method for older browsers
function fallbackCopy(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = '0';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showNotification('已复制到剪贴板', 'success');
} else {
showNotification('复制失败', 'error');
}
} catch (err) {
console.error('复制失败:', err);
showNotification('复制失败', 'error');
}
document.body.removeChild(textArea);
}
// Loading Overlay
let loadingTimeout = null;
export function showLoading(message = '加载中...') {
const overlay = document.getElementById('loadingOverlay');
const textEl = overlay.querySelector('.loading-text');
if (textEl) {
textEl.textContent = message;
}
// 延迟显示,避免快速请求闪烁
clearTimeout(loadingTimeout);
loadingTimeout = setTimeout(() => {
overlay.classList.add('active');
}, 100);
}
export function hideLoading() {
clearTimeout(loadingTimeout);
const overlay = document.getElementById('loadingOverlay');
overlay.classList.remove('active');
}
// Fetch wrapper with automatic loading
export async function fetchWithLoading(url, options = {}, loadingMessage = '加载中...') {
const adminToken = localStorage.getItem('adminToken');
// Add admin token to headers
const headers = {
...options.headers,
'x-admin-token': adminToken
};
// Show loading
showLoading(loadingMessage);
try {
const response = await fetch(url, {
...options,
headers
});
return response;
} finally {
hideLoading();
}
}