/** * OSS Community Module * Handles rendering of categories, stats, and posts list. * Designed to be SPA-friendly (re-initializable). */ // Global state variables let currentPage = 1; let currentCategory = ''; let currentSort = 'latest'; // Expose initialization function globally for SPA Router window.initCommunity = function() { // Check if we are deep-linked (e.g. ?page=2) const params = new URLSearchParams(window.location.search); if(params.has('page')) currentPage = parseInt(params.get('page')); // Fetch and Render Data loadCategories(); loadStats(); loadPosts(); // Bind Events (to the new DOM elements created by SPA) bindCommunityEvents(); }; function bindCommunityEvents() { const catList = document.getElementById('categoryList'); if (catList) { catList.addEventListener('click', e => { const li = e.target.closest('li'); if (!li) return; document.querySelectorAll('.category-list li').forEach(el => el.classList.remove('active')); li.classList.add('active'); currentCategory = li.dataset.id || ''; currentPage = 1; document.getElementById('currentCategory').textContent = li.querySelector('span:nth-child(2)').textContent + '帖子'; loadPosts(); }); } const sortOptions = document.querySelector('.sort-options'); if (sortOptions) { sortOptions.addEventListener('click', e => { if (!e.target.classList.contains('sort-btn')) return; document.querySelectorAll('.sort-btn').forEach(btn => btn.classList.remove('active')); e.target.classList.add('active'); currentSort = e.target.dataset.sort; currentPage = 1; loadPosts(); }); } } // --- Data Loading Functions --- const API = './api/index.php'; // Relative to community/ directory async function loadCategories() { try { const res = await fetch(`${API}?action=categories`); const data = await res.json(); const list = document.getElementById('categoryList'); if (!list) return; // Keep the "All" item (first child) if it exists const allItem = list.firstElementChild; list.innerHTML = ''; if (allItem) list.appendChild(allItem); data.categories.forEach(cat => { const li = document.createElement('li'); li.dataset.id = cat.id; li.innerHTML = `${getIconSvg(cat.icon)}${cat.name}`; list.appendChild(li); }); } catch (e) { console.error('Failed to load categories', e); } } async function loadStats() { try { const res = await fetch(`${API}?action=stats`); const data = await res.json(); animateValue('statPosts', 0, data.posts, 1000); animateValue('statReplies', 0, data.replies, 1000); animateValue('statUsers', 0, data.users, 1000); const countAll = document.getElementById('countAll'); if (countAll) countAll.textContent = data.posts; } catch (e) { console.error('Failed to load stats', e); } } async function loadPosts() { const list = document.getElementById('postsList'); if (!list) return; list.innerHTML = '

正在连接节点...

'; try { const params = new URLSearchParams({ action: 'posts', page: currentPage }); if (currentCategory) params.append('category_id', currentCategory); const res = await fetch(`${API}?${params}`); const data = await res.json(); list.innerHTML = ''; if (data.posts.length === 0) { list.innerHTML = `
📭

暂无帖子

成为第一个发帖的人吧!

`; return; } data.posts.forEach((post, index) => { const card = document.createElement('div'); card.className = 'post-card'; card.dataset.postId = post.id; card.style.animationDelay = `${index * 0.1}s`; card.innerHTML = `
${post.username[0].toUpperCase()}
${timeAgo(post.created_at)}
${post.category_name} ${post.is_pinned ? ' 置顶' : ''}
${escapeHtml(post.title)}
${escapeHtml(post.content.substring(0, 150))}...
${post.views} ${post.likes} ${post.reply_count || 0}
`; list.appendChild(card); }); renderPagination(data.pages); } catch (e) { list.innerHTML = '

加载失败,请稍后重试

'; } } function renderPagination(pages) { const container = document.getElementById('pagination'); if (!container) return; container.innerHTML = ''; if (pages <= 1) return; for (let i = 1; i <= pages; i++) { const btn = document.createElement('button'); btn.className = `page-btn ${i === currentPage ? 'active' : ''}`; btn.textContent = i; btn.onclick = () => { currentPage = i; loadPosts(); window.scrollTo({ top: 0, behavior: 'smooth' }); }; container.appendChild(btn); } } // --- Utilities --- function animateValue(id, start, end, duration) { const obj = document.getElementById(id); if (!obj) return; let startTimestamp = null; const step = (timestamp) => { if (!startTimestamp) startTimestamp = timestamp; const progress = Math.min((timestamp - startTimestamp) / duration, 1); obj.textContent = Math.floor(progress * (end - start) + start); if (progress < 1) { window.requestAnimationFrame(step); } }; window.requestAnimationFrame(step); } function timeAgo(dateStr) { const now = new Date(); const date = new Date(dateStr); const seconds = Math.floor((now - date) / 1000); const intervals = [ [31536000, '年'], [2592000, '个月'], [604800, '周'], [86400, '天'], [3600, '小时'], [60, '分钟'] ]; for (const [secondsCount, label] of intervals) { const count = Math.floor(seconds / secondsCount); if (count >= 1) return `${count}${label}前`; } return '刚刚'; } function escapeHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function getIconSvg(name) { const icons = { megaphone: '', question: '', chat: '', puzzle: '', bug: '' }; return icons[name] || icons['chat']; } // showCreateModal 函数已移至 post-modal.php 中定义 // 此处仅保留兼容性封装 window.showCreateModal = window.showCreateModal || function() { if (typeof window.showCreatePostModal === 'function') { window.showCreatePostModal(); } else { console.warn('发帖模态框未加载'); } }; // Auto-init if not loaded via SPA (Hard refresh case) document.addEventListener('DOMContentLoaded', () => { if (typeof AppRouter === 'undefined') { window.initCommunity(); } });