// 宇森官网 · 公开页数据打通(图鉴/规则/论坛/公告 从 Odoo 取数) // 依赖 assets/game-api.js // ---------- 规则 ---------- async function loadRules(){ const el = document.querySelector('#rules .rules-scroll'); if (!el) return; try { const d = await API.rules(); const groups = (d && d.groups) || []; if (!groups.length){ el.innerHTML = '
暂无规则数据
'; return; } el.innerHTML = groups.map(g => { const rows = (g.items || []).map(it => { const lvl = (it.level != null) ? ('Lv.' + it.level) : (g.key === 'world' ? '阶段' : ''); return `
${lvl}${it.name}${it.desc || ''}
`; }).join(''); return `

${g.label}

${rows}
`; }).join(''); } catch (e) { /* 保留静态兜底内容 */ } } // ---------- 论坛 ---------- async function loadForum(){ const threadsEl = document.getElementById('threads'); const catsEl = document.querySelector('#forum .f-cats'); if (!threadsEl) return; try { const list = await API.threads(); if (!list || !list.length){ threadsEl.innerHTML = '
暂无主题
'; return; } // 分类筛选 const cats = []; list.forEach(t => { if (t.category && cats.indexOf(t.category) < 0) cats.push(t.category); }); if (catsEl){ catsEl.innerHTML = `
全部
` + cats.map(c => `
${c}
`).join(''); catsEl.querySelectorAll('.ch').forEach(b => b.addEventListener('click', () => { catsEl.querySelectorAll('.ch').forEach(x => x.classList.remove('active')); b.classList.add('active'); const c = b.dataset.fcat; threadsEl.querySelectorAll('.th').forEach(t => { t.style.display = (c === 'all' || t.dataset.fcat === c) ? '' : 'none'; }); })); } threadsEl.innerHTML = list.map(t => `
` + (t.pinned ? `置顶` : '') + `${t.name}` + `${(t.replies || []).length} 回复 · ${t.author_name || '匿名'}
` ).join(''); } catch (e) { /* 保留静态兜底内容 */ } } // ---------- 公告(卡片 + 弹窗) ---------- function annEscapeHtml(s) { return String(s == null ? '' : s) .replace(/&/g, '&').replace(//g, '>') .replace(/"/g, '"').replace(/'/g, '''); } function annFormatDate(d) { // "2026-07-09" -> "2026.07.09" return d ? String(d).replace(/-/g, '.') : ''; } function annStripHtml(html) { const tmp = document.createElement('div'); tmp.innerHTML = html || ''; return (tmp.textContent || '').replace(/\s+/g, ' ').trim(); } async function loadAnnounce(){ const root = document.getElementById('announce'); if (!root) return; root.innerHTML = '
加载中…
'; let list = []; try { list = await API.announcements(); } catch (e) { /* 落到空态 */ } if (!list || !list.length) { root.innerHTML = '
暂无公告
'; return; } root.innerHTML = list.map((n, i) => { const pri = n.priority || 'normal'; const preview = annStripHtml(n.body_html).slice(0, 160); return `
${annEscapeHtml(annFormatDate(n.date))} ${annEscapeHtml(n.priority_label || pri)}
${annEscapeHtml(n.name)}
${annEscapeHtml(preview)}
阅读全文
`; }).join(''); root.querySelectorAll('.announce-card').forEach(card => { const idx = +card.dataset.idx; const open = () => openAnnounceModal(list[idx]); card.addEventListener('click', open); card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); } }); }); } // ---- 弹窗(懒创建一次) ---- let _annModal = null; let _annLastFocus = null; function ensureAnnounceModal() { if (_annModal) return _annModal; _annModal = document.createElement('div'); _annModal.className = 'announce-modal'; _annModal.setAttribute('role', 'presentation'); _annModal.innerHTML = `
`; document.body.appendChild(_annModal); _annModal.addEventListener('click', e => { if (e.target.closest('[data-close]')) closeAnnounceModal(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape' && _annModal.classList.contains('open')) closeAnnounceModal(); }); return _annModal; } function openAnnounceModal(item) { if (!item) return; const m = ensureAnnounceModal(); m.querySelector('[data-title]').textContent = item.name || ''; m.querySelector('[data-meta]').textContent = [annFormatDate(item.date), item.priority_label].filter(Boolean).join(' · '); // body_html 来自后台管理员撰写,可信;非管理员输入路径不存在,OK 直接渲染 m.querySelector('[data-body]').innerHTML = item.body_html || '

(无正文)

'; _annLastFocus = document.activeElement; m.classList.add('open'); document.body.style.overflow = 'hidden'; // 焦点移入弹窗(关闭按钮) const closeBtn = m.querySelector('.close'); if (closeBtn) closeBtn.focus({ preventScroll: true }); } function closeAnnounceModal() { if (!_annModal || !_annModal.classList.contains('open')) return; _annModal.classList.remove('open'); document.body.style.overflow = ''; if (_annLastFocus && _annLastFocus.focus) _annLastFocus.focus({ preventScroll: true }); } document.addEventListener('DOMContentLoaded', () => { loadRules(); loadForum(); loadAnnounce(); });