Files
PerToolBoxFront/js/common.js

215 lines
6.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.
/**
* PerToolBox Front - 公共 JavaScript
*/
// ========== 全局变量 ==========
let currentUser = null;
// ========== 工具函数 ==========
function getToken() {
return localStorage.getItem('token');
}
function setToken(token) {
if (token) {
localStorage.setItem('token', token);
} else {
localStorage.removeItem('token');
}
}
function isLoggedIn() {
return !!getToken();
}
// ========== API 请求封装 ==========
async function apiRequest(endpoint, options = {}) {
const baseUrl = window.API_BASE || '/api/v1';
const url = endpoint.startsWith('http') ? endpoint : `${baseUrl}${endpoint}`;
// 默认 headers
const headers = {
...options.headers
};
// 如果没有设置 Content-Type 且 body 是 URLSearchParams不自动添加
if (!options.headers || !options.headers['Content-Type']) {
if (options.body && options.body instanceof URLSearchParams) {
// 表单数据,不添加 JSON Content-Type
headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else if (options.body && typeof options.body === 'string') {
// 已经是字符串,不添加
} else if (options.body && typeof options.body === 'object') {
// JSON 对象
headers['Content-Type'] = 'application/json';
options.body = JSON.stringify(options.body);
}
}
const token = getToken();
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
try {
const response = await fetch(url, {
...options,
headers
});
if (response.status === 401) {
setToken(null);
if (!window.location.pathname.includes('/login.php')) {
window.location.href = '/login.php';
}
return null;
}
// 获取响应文本
const text = await response.text();
if (!response.ok) {
let errorMsg;
try {
const errorData = JSON.parse(text);
errorMsg = errorData.detail || errorData.message || JSON.stringify(errorData);
} catch (e) {
errorMsg = text || '请求失败';
}
throw new Error(errorMsg);
}
try {
return JSON.parse(text);
} catch (e) {
return text;
}
} catch (error) {
console.error('API 请求错误:', error);
throw error;
}
}
// ========== 热度上报 ==========
async function recordUsage(toolName) {
try {
await apiRequest(`/tool/usage?tool_name=${toolName}`, { method: 'POST' });
console.log(`✅ 热度上报: ${toolName}`);
} catch (error) {
console.warn('热度上报失败:', error);
}
}
// ========== 获取用户信息 ==========
async function loadUserInfo() {
if (!isLoggedIn()) {
updateUserUI(null);
return null;
}
try {
const user = await apiRequest('/user/profile');
currentUser = user;
updateUserUI(user);
return user;
} catch (error) {
console.error('获取用户信息失败:', error);
setToken(null);
updateUserUI(null);
return null;
}
}
// ========== 更新侧边栏 UI ==========
function updateUserUI(user) {
const userInfoDiv = document.getElementById('userInfo');
const profileLink = document.getElementById('profileLink');
const logoutBtn = document.getElementById('logoutBtn');
const loginLink = document.getElementById('loginLink');
if (user) {
const displayName = user.username || user.phone || user.email || '用户';
if (userInfoDiv) {
userInfoDiv.innerHTML = `
<div class="px-6 py-3 bg-blue-800 rounded-lg mx-4 mb-2">
<div class="text-sm text-blue-200">欢迎,</div>
<div class="font-semibold">${escapeHtml(displayName)}</div>
</div>
`;
}
if (profileLink) profileLink.style.display = 'flex';
if (logoutBtn) logoutBtn.style.display = 'flex';
if (loginLink) loginLink.style.display = 'none';
} else {
if (userInfoDiv) userInfoDiv.innerHTML = '';
if (profileLink) profileLink.style.display = 'none';
if (logoutBtn) logoutBtn.style.display = 'none';
if (loginLink) loginLink.style.display = 'flex';
}
}
// ========== 退出登录 ==========
function logout() {
setToken(null);
currentUser = null;
updateUserUI(null);
window.location.href = '/';
}
// ========== 侧边栏控制 ==========
function initSidebar() {
const menuBtn = document.getElementById('menuBtn');
const closeBtn = document.getElementById('closeSidebar');
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('overlay');
function openSidebar() {
if (sidebar) sidebar.classList.add('open');
if (overlay) overlay.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeSidebar() {
if (sidebar) sidebar.classList.remove('open');
if (overlay) overlay.classList.remove('active');
document.body.style.overflow = '';
}
if (menuBtn) menuBtn.addEventListener('click', openSidebar);
if (closeBtn) closeBtn.addEventListener('click', closeSidebar);
if (overlay) overlay.addEventListener('click', closeSidebar);
}
// ========== HTML 转义 ==========
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// ========== 显示 Toast 消息 ==========
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `fixed bottom-4 right-4 z-50 px-4 py-2 rounded-lg shadow-lg text-white ${
type === 'success' ? 'bg-green-500' : 'bg-red-500'
}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
// ========== 页面初始化 ==========
document.addEventListener('DOMContentLoaded', () => {
initSidebar();
loadUserInfo();
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', (e) => {
e.preventDefault();
logout();
});
}
});