window.C2CStore = (function() { const API_BASE = window.C2C_API_BASE || '/api'; const TOKEN_KEY = 'c2c_token'; const USER_KEY = 'c2c_user'; function getStore(key, fallback) { try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : (fallback || []); } catch (e) { return fallback || []; } } function setStore(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (e) { console.warn('localStorage 写入失败', e); } return value; } function upsertById(key, item, max) { const list = getStore(key, []); const next = [item, ...list.filter(x => String(x.id) !== String(item.id))]; const clipped = max ? next.slice(0, max) : next; return setStore(key, clipped); } function removeById(key, id) { const list = getStore(key, []); return setStore(key, list.filter(x => String(x.id) !== String(id))); } function seedIfEmpty(key, data) { const list = getStore(key, []); if (list.length) return list; return setStore(key, data || []); } function clearStore(key) { return setStore(key, []); } function formatDate(ts) { if (!ts) return '刚刚'; const d = new Date(ts); const now = new Date(); const diff = now - d; const day = 24 * 60 * 60 * 1000; if (diff < 60 * 1000) return '刚刚'; if (diff < 60 * 60 * 1000) return Math.floor(diff / 60000) + ' 分钟前'; if (diff < day) return Math.floor(diff / 3600000) + ' 小时前'; if (diff < day * 7) return Math.floor(diff / day) + ' 天前'; return `${d.getMonth() + 1}/${d.getDate()}`; } function toast(msg) { let el = document.getElementById('toast'); if (!el) { el = document.createElement('div'); el.id = 'toast'; el.className = 'toast'; document.body.appendChild(el); } el.textContent = msg; el.classList.add('show'); clearTimeout(window.__c2cToastTimer); window.__c2cToastTimer = setTimeout(() => el.classList.remove('show'), 2200); } function setToken(token) { if (token) localStorage.setItem(TOKEN_KEY, token); else localStorage.removeItem(TOKEN_KEY); } function getToken() { return localStorage.getItem(TOKEN_KEY) || ''; } function setCurrentUser(user) { if (!user) { localStorage.removeItem(USER_KEY); localStorage.removeItem('c2c_currentUser'); return null; } setStore(USER_KEY, user); localStorage.setItem('c2c_currentUser', user.username || ''); return user; } function getCurrentUser() { try { return JSON.parse(localStorage.getItem(USER_KEY) || 'null'); } catch (e) { return null; } } function logout() { setToken(''); setCurrentUser(null); } async function api(path, options) { const opts = options || {}; const headers = Object.assign({}, opts.headers || {}); const token = getToken(); if (token) headers.Authorization = 'Bearer ' + token; const hasBody = opts.body !== undefined && opts.body !== null; const isFormData = typeof FormData !== 'undefined' && opts.body instanceof FormData; if (hasBody && !isFormData && !headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } const res = await fetch(API_BASE + path, Object.assign({}, opts, { headers })); let data = null; try { data = await res.json(); } catch (e) { data = null; } if (!res.ok) { const err = new Error((data && data.error) || '请求失败'); err.status = res.status; err.payload = data; throw err; } return data; } async function fetchMe() { const data = await api('/me'); if (data && data.user) setCurrentUser(data.user); return data && data.user; } function requireLogin(nextUrl) { if (getToken()) return true; const target = nextUrl || location.href; location.href = 'login.html?next=' + encodeURIComponent(target); return false; } function getNextUrl(defaultUrl) { const q = new URLSearchParams(location.search); return q.get('next') || defaultUrl || 'circle.html'; } return { API_BASE, TOKEN_KEY, USER_KEY, getStore, setStore, upsertById, removeById, seedIfEmpty, clearStore, formatDate, toast, api, setToken, getToken, setCurrentUser, getCurrentUser, fetchMe, logout, requireLogin, getNextUrl }; })();