game/website/assets/site-app.js
李鹏宇 760cd517c2 feat: 规则货币合并+规则类别配置菜单 + 世界数据大报表 + 背景/论坛同步
后端 game_base:
- 货币并入 game.rule(rate/note 字段),新增可配置规则类别 game.rule.category(world/faction/character/currency)
- 新增「配置」菜单与规则类别配置子项
- /game/api/rules 按类别分组返回,/game/api/currencies 保持兼容
- 列表分组取消等级累计(aggregator=False,适配 Odoo18)
- 1.2 pre-migrate 清理旧 game.currency 记录,避免外部ID冲突

前端/官网:
- 世界数据大报表(index.html + .wd-* 样式)
- 背景 5 板块同步、论坛汉化、过渡资源与 views 拆分(announcement/codex/forum/page/rule/story)
2026-07-24 17:58:17 +08:00

164 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 宇森官网 · 公开页数据打通(图鉴/规则/论坛/公告 从 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 = '<div class="tdesc">暂无规则数据</div>'; return; }
el.innerHTML = groups.map(g => {
const rows = (g.items || []).map(it => {
const lvl = (it.level != null) ? ('Lv.' + it.level) : (g.key === 'world' ? '阶段' : '');
return `<div class="tier-row"><span class="tn">${lvl}</span><span class="tname">${it.name}</span><span class="tdesc">${it.desc || ''}</span></div>`;
}).join('');
return `<div class="rule-block"><h3>${g.label}</h3><div class="tier-list">${rows}</div></div>`;
}).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 = '<div class="th"><span class="tt">暂无主题</span></div>'; return; }
// 分类筛选
const cats = [];
list.forEach(t => { if (t.category && cats.indexOf(t.category) < 0) cats.push(t.category); });
if (catsEl){
catsEl.innerHTML = `<div class="ch active" data-fcat="all">全部</div>` +
cats.map(c => `<div class="ch" data-fcat="${c}">${c}</div>`).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 =>
`<div class="th" data-fcat="${t.category || 'all'}">` +
(t.pinned ? `<span class="pin">置顶</span>` : '') +
`<span class="tt">${t.name}</span>` +
`<span class="meta"><span class="rp">${(t.replies || []).length}</span> 回复 · ${t.author_name || '匿名'}</span></div>`
).join('');
} catch (e) { /* 保留静态兜底内容 */ }
}
// ---------- 公告(卡片 + 弹窗) ----------
function annEscapeHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
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 = '<div class="loading">加载中…</div>';
let list = [];
try {
list = await API.announcements();
} catch (e) { /* 落到空态 */ }
if (!list || !list.length) {
root.innerHTML = '<div class="announce-empty">暂无公告</div>';
return;
}
root.innerHTML = list.map((n, i) => {
const pri = n.priority || 'normal';
const preview = annStripHtml(n.body_html).slice(0, 160);
return `
<div class="announce-card priority-${annEscapeHtml(pri)}"
data-idx="${i}" role="button" tabindex="0"
aria-label="阅读公告: ${annEscapeHtml(n.name)}">
<div class="meta-row">
<span class="date">${annEscapeHtml(annFormatDate(n.date))}</span>
<span class="dot"></span>
<span class="badge ${annEscapeHtml(pri)}">${annEscapeHtml(n.priority_label || pri)}</span>
</div>
<div class="title">${annEscapeHtml(n.name)}</div>
<div class="preview">${annEscapeHtml(preview)}</div>
<div class="more">阅读全文 <span class="arrow">→</span></div>
</div>`;
}).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 = `
<div class="backdrop" data-close="1" aria-label="关闭"></div>
<div class="dialog" role="dialog" aria-modal="true" aria-labelledby="ann-modal-title">
<div class="head">
<div>
<div class="title" id="ann-modal-title" data-title></div>
<div class="meta" data-meta></div>
</div>
<button class="close" type="button" data-close="1" aria-label="关闭">×</button>
</div>
<div class="body" data-body></div>
</div>`;
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 || '<p style="color:#556677">(无正文)</p>';
_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();
});