const API_BASE = ''; // 使用相对路径 function readTokenPayload(token) { try { const payload = token.split('.')[1]; if (!payload) return null; const normalizedPayload = payload.replace(/-/g, '+').replace(/_/g, '/'); const paddedPayload = normalizedPayload.padEnd( normalizedPayload.length + ((4 - (normalizedPayload.length % 4)) % 4), '=' ); return JSON.parse(atob(paddedPayload)); } catch (err) { return null; } } function getValidToken() { if (typeof window === 'undefined') return null; const token = localStorage.getItem('token'); if (!token) return null; const payload = readTokenPayload(token); if (!payload?.exp || payload.exp * 1000 <= Date.now()) { localStorage.removeItem('token'); return null; } return token; } function redirectToLogin() { if (typeof window === 'undefined') return; localStorage.removeItem('token'); if (window.location.pathname !== '/login') { window.location.replace('/login'); } } function getAuthHeaders() { const token = getValidToken(); return token ? { Authorization: `Bearer ${token}` } : {}; } async function apiCall(url, options = {}) { const res = await fetch(`${API_BASE}${url}`, { ...options, headers: { 'Content-Type': 'application/json', ...getAuthHeaders(), ...options.headers, }, }); const contentType = res.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { const text = await res.text(); throw new Error(`服务器错误: ${res.status} - ${text.substring(0, 100)}`); } const data = await res.json(); if (!res.ok) { if (res.status === 401 && !url.startsWith('/api/auth/')) { redirectToLogin(); } throw new Error(data.error || '请求失败'); } return data; } // 认证 export async function login(username, password) { const data = await apiCall('/api/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }); if (typeof window !== 'undefined') { localStorage.setItem('token', data.token); } return data; } export async function register(username, password) { const data = await apiCall('/api/auth/register', { method: 'POST', body: JSON.stringify({ username, password }), }); if (typeof window !== 'undefined') { localStorage.setItem('token', data.token); } return data; } export function logout() { if (typeof window !== 'undefined') { localStorage.removeItem('token'); } } export function isLoggedIn() { return !!getValidToken(); } // 账目 export async function getRecords(month, type = 'all') { let url = `/api/records?month=${month}`; if (type !== 'all') { url += `&type=${type}`; } return apiCall(url); } export async function getStats(month) { return apiCall(`/api/stats?month=${month}`); } export async function createRecord(data) { return apiCall('/api/records', { method: 'POST', body: JSON.stringify(data), }); } export async function updateRecord(id, data) { return apiCall(`/api/records/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } export async function deleteRecord(id) { return apiCall(`/api/records/${id}`, { method: 'DELETE', }); } // 批量删除指定分类的记录 export async function deleteRecordsByCategory(category) { return apiCall(`/api/records/by-category/${encodeURIComponent(category)}`, { method: 'DELETE', }); } // 搜索 export async function searchRecords(keyword) { return apiCall(`/api/search?q=${encodeURIComponent(keyword)}`); } // 统计 export async function getTrendStats(period = 'month', month) { let url = `/api/stats/trend?period=${period}`; if (month) url += `&month=${month}`; return apiCall(url); } export async function getCategoryStats(month, type = 'all') { let url = `/api/stats/category?type=${type}`; if (month) url += `&month=${month}`; return apiCall(url); } // 周期性账单 export async function getRecurringBills() { return apiCall('/api/recurring'); } export async function createRecurringBill(data) { return apiCall('/api/recurring', { method: 'POST', body: JSON.stringify(data), }); } export async function updateRecurringBill(id, data) { return apiCall(`/api/recurring/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } export async function deleteRecurringBill(id) { return apiCall(`/api/recurring/${id}`, { method: 'DELETE', }); } export async function applyRecurringBill(id) { return apiCall(`/api/recurring/${id}/apply`, { method: 'POST', }); } // 分类 export async function getCategories() { return apiCall('/api/categories'); } export async function createCategory(data) { return apiCall('/api/categories', { method: 'POST', body: JSON.stringify(data), }); } export async function deleteCategory(id) { return apiCall(`/api/categories/${id}`, { method: 'DELETE', }); } // 数据导入导出 export async function exportData() { return apiCall('/api/export'); } export async function importData(data) { return apiCall('/api/export', { method: 'POST', body: JSON.stringify(data), }); }