// 宇森官网 · 公开页数据打通(图鉴/规则/论坛/公告 从 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 ``;
}).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); });
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();
loadForum();
loadAnnounce();
loadStory();
});