// 宇森官网 · 公开页数据打通(图鉴/规则/论坛/公告 从 Odoo 取数) // 依赖 assets/game-api.js // ---------- 页面板块:特色 / 背景 / 致玩家 合并进「背景」单页(平铺,非子菜单) ---------- function featuresHTML(p){ const items = (p.items || []).map(it => `
${it.icon || ''}

${it.title || ''}

${it.desc || ''}

` ).join(''); return `
${p.title || '游戏特色'}${p.subtitle || ''}
${items}
`; } function backgroundHTML(p){ const layers = (p.items || []).map((it, idx) => `
${it.title || ''}
${it.desc || ''}
${it.icon || '✦'}
` ).join(''); return `
${layers}

${p.title || '世界起源'}

${p.body_html || ''}
`; } function letterHTML(p){ return `

${p.title || ''}

${p.subtitle || ''}
${p.body_html || ''}
—— 宇森开发组
`; } async function loadPages(){ try { const d = await API.pages(); const pages = (d && d.items) || []; const bg = document.getElementById('background'); let html = ''; pages.forEach(p => { if (p.key === 'features') html += featuresHTML(p); else if (p.key === 'background') html += backgroundHTML(p); else if (p.key === 'letter') html += letterHTML(p); }); if (bg) bg.innerHTML = html; // 特色 + 背景 + 致玩家 平铺进同一页,无子菜单切换 } catch (e) { /* 失败时保留 HTML 中的静态兜底内容 */ } } // ---------- 规则 ---------- 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){ 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 loadCodex(){ const grid = document.getElementById('codexGrid'); const catsEl = document.getElementById('codexCats'); const subsEl = document.getElementById('codexSubs'); const searchEl= document.getElementById('codexSearch'); const countEl = document.getElementById('codexCount'); if (!grid) return; let data; try { data = await API.codex(); } catch (e) { return; } // 失败时保留静态兜底内容 const groups = (data && data.groups) || []; if (!groups.length){ grid.innerHTML = '
暂无图鉴数据
'; return; } // 选中状态 let curGroup = groups[0].key; let curSub = 'all'; let curQ = ''; function renderCats(){ if (!catsEl) return; catsEl.innerHTML = groups.map(g => `` ).join(''); catsEl.querySelectorAll('button').forEach(b => b.addEventListener('click', () => { curGroup = b.dataset.gk; curSub = 'all'; curQ = ''; if (searchEl) searchEl.value = ''; renderCats(); renderSubs(); renderGrid(); })); } function renderSubs(){ if (!subsEl) return; const g = groups.find(x => x.key === curGroup); const subs = (g && g.subs) || []; subsEl.innerHTML = `` + subs.map(s => ``).join(''); subsEl.querySelectorAll('button').forEach(b => b.addEventListener('click', () => { curSub = b.dataset.sn; renderSubs(); renderGrid(); })); } function renderGrid(){ const g = groups.find(x => x.key === curGroup); if (!g){ grid.innerHTML = ''; return; } // 按子分类筛选 let items = []; (g.subs || []).forEach(s => { (s.items || []).forEach(it => { if (curSub !== 'all' && it.sub !== curSub) return; items.push(it); }); }); // 搜索过滤 if (curQ){ const q = curQ.toLowerCase(); items = items.filter(it => (it.name || '').toLowerCase().includes(q) || (it.desc || '').replace(/<[^>]+>/g, '').toLowerCase().includes(q) ); } if (countEl) countEl.textContent = items.length + ' 项'; if (!items.length){ grid.innerHTML = '
未找到匹配项
'; return; } const base = API.base || ''; grid.innerHTML = items.map(it => { const thumb = it.image ? `
` : `
`; return `
${thumb}
${it.name || ''}
${it.desc || ''}
${it.sub || ''}${it.tier || ''}
`; }).join(''); } if (searchEl){ searchEl.addEventListener('input', () => { curQ = searchEl.value.trim(); renderGrid(); }); } renderCats(); renderSubs(); renderGrid(); } // ---------- 论坛 ---------- 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); }); const catLabels = {}; list.forEach(t => { if (t.category) catLabels[t.category] = t.category_label || t.category; }); if (catsEl){ catsEl.innerHTML = `
全部
` + cats.map(c => `
${catLabels[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) { /* 保留静态兜底内容 */ } } // ---------- 公告 ---------- async function loadAnnounce(){ const el = document.querySelector('#announce .notes'); if (!el) return; try { const list = await API.announcements(); if (!list || !list.length){ el.innerHTML = '
暂无公告
'; return; } el.innerHTML = list.map((n, i) => { // 封面图:后台返回 image 字段则铺满封面;否则渐变占位(图片位从后台取) const cover = n.image ? `
` : `
`; const title = n.title ? `
${n.title}
` : ''; const feat = (i === 0) ? ' feat' : ''; return `
${cover}
${n.date || ''}
${title}
${n.body_html || ''}
`; }).join(''); } catch (e) { /* 保留静态兜底内容 */ } } // ---------- 剧情(小说阅读器:左侧目录 + 右侧正文) ---------- async function loadStory(){ const tocList = document.getElementById('storyTocList'); const reader = document.getElementById('storyReader'); if (!tocList || !reader) return; let data; try { data = await API.story(); } catch (e) { tocList.innerHTML = '
目录加载失败
'; return; } const volumes = (data && data.volumes) || []; if (!volumes.length){ tocList.innerHTML = '
暂无内容
'; return; } const volNameByCh = {}; let firstCid = null; const html = volumes.map(v => { const chs = (v.chapters || []).map(c => { volNameByCh[c.id] = v.name; if (firstCid === null) firstCid = c.id; return ''; }).join(''); return '
' + v.name + '
' + (v.intro ? '
' + v.intro + '
' : '') + chs + '
'; }).join(''); tocList.innerHTML = html; async function openChapter(cid){ tocList.querySelectorAll('.toc-ch').forEach(b => b.classList.toggle('active', String(b.dataset.cid) === String(cid))); reader.innerHTML = '
加载中…
'; try { const d = await API.storyChapter(cid); if (!d || d.error){ reader.innerHTML = '
' + ((d && d.error) || '章节加载失败') + '
'; return; } reader.innerHTML = '
' + (volNameByCh[cid] || '') + '
' + '

' + d.name + '

' + (d.body_html || '

(暂无正文)

') + '
'; reader.scrollTop = 0; } catch (e) { reader.innerHTML = '
章节加载失败
'; } } tocList.querySelectorAll('.toc-ch').forEach(b => b.addEventListener('click', () => openChapter(b.dataset.cid))); if (firstCid !== null) openChapter(firstCid); } document.addEventListener('DOMContentLoaded', () => { loadPages(); loadRules(); loadCodex(); loadForum(); loadAnnounce(); loadStory(); });