// 宇森游戏 · 前端与 Odoo 通信层 // 本地调试:前端 8080/8888 -> Odoo 8069(跨域,带调试 CORS 头) // 生产部署:前端与 Odoo 同域,由 nginx 代理 /game/*。 // 库名说明:本地 Odoo 用 odoo.conf 钉死 yt_game;线上游戏库名为 game(dbfilter 不含 yt_game)。 // game/* 走统一 API_CONFIG.db;yt_world/* 单独带 ?db=,按 WORLD_DB 区分本地/线上,避免 git 同步互相覆盖。 const _isLocalDev = () => location.hostname === '127.0.0.1' || location.hostname === 'localhost'; const _isDevPort = (port) => location.port === String(port) || // 某些环境(如 Python http.server)可能不设 port(默认 80)或用空串 (port === 80 && (location.port === '' || location.port === '80')); const API_CONFIG = { base: (_isLocalDev() && (_isDevPort(8080) || _isDevPort(8888))) ? `http://${location.hostname}:8069` : '', db: (_isLocalDev() && (_isDevPort(8080) || _isDevPort(8888))) ? 'study' : 'game', }; // 世界(yt_world)所在库:本地 Odoo 钉 yt_game;线上游戏库名为 game。按域名自适应,git 同步不冲突。 const WORLD_DB = _isLocalDev() ? 'yt_game' : 'game'; // Odoo 后端基础 URL(世界 API 专用,兼容本地跨域 / 生产同域) const WORLD_BASE = API_CONFIG.base || ''; const _cred = API_CONFIG.base ? 'include' : 'same-origin'; const _wcred = WORLD_BASE ? 'include' : 'same-origin'; function _url(path) { return `${API_CONFIG.base}${path}?db=${API_CONFIG.db}`; } function _worldUrl(path) { return `${WORLD_BASE}${path}?db=${WORLD_DB}`; } const API = { async me() { return (await fetch(_url('/game/api/me'), { credentials: _cred })).json(); }, async login(login, password) { return (await fetch(_url('/game/api/login'), { method: 'POST', credentials: _cred, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ login, password }), })).json(); }, async register(payload) { return (await fetch(_url('/game/api/register'), { method: 'POST', credentials: _cred, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), })).json(); }, async logout() { return (await fetch(_url('/game/api/logout'), { method: 'POST', credentials: _cred, })).json(); }, async codex() { return (await fetch(_url('/game/api/codex'), { credentials: _cred })).json(); }, async rules() { return (await fetch(_url('/game/api/rules'), { credentials: _cred })).json(); }, async currencies() { return (await fetch(_url('/game/api/currencies'), { credentials: _cred })).json(); }, async pages() { return (await fetch(_url('/game/api/pages'), { credentials: _cred })).json(); }, async threads() { return (await fetch(_url('/game/api/threads'), { credentials: _cred })).json(); }, async announcements() { return (await fetch(_url('/game/api/announcements'), { credentials: _cred })).json(); }, async story() { return (await fetch(_url('/game/api/story'), { credentials: _cred })).json(); }, async storyChapter(cid) { return (await fetch(_url('/game/api/story/chapter/' + cid), { credentials: _cred })).json(); }, // ---------- 沙盘记忆(yt_world 模块) ---------- async world() { return (await fetch(_worldUrl('/yt_world/api/world'), { credentials: _wcred })).json(); }, async saveTiles(tiles) { return (await fetch(_worldUrl('/yt_world/api/tiles/save'), { method: 'POST', credentials: _wcred, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tiles }), })).json(); }, async saveSettings(settings) { return (await fetch(_worldUrl('/yt_world/api/settings'), { method: 'POST', credentials: _wcred, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ settings }), })).json(); }, async renameWorld(name) { return (await fetch(_worldUrl('/yt_world/api/world/rename'), { method: 'POST', credentials: _wcred, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }), })).json(); }, // ---------- 世界数据 / 包裹(后端接口预留:yt_world 暂未实现路由,前端 mock 兜底) ---------- async worldData() { try { const r = await fetch(_worldUrl('/yt_world/api/data'), { credentials: _wcred }); if (!r.ok) return null; return await r.json(); } catch (e) { return null; } }, async inventory() { try { const r = await fetch(_worldUrl('/yt_world/api/inventory'), { credentials: _wcred }); if (!r.ok) return null; return await r.json(); } catch (e) { return null; } }, // ---------- 图鉴(codex) ---------- async codexResources() { return (await fetch(_worldUrl('/yt_world/api/codex/resources'), { credentials: _wcred })).json(); }, async codexCreatures() { return (await fetch(_worldUrl('/yt_world/api/codex/creatures'), { credentials: _wcred })).json(); }, async codexMonsters() { return (await fetch(_worldUrl('/yt_world/api/codex/monsters'), { credentials: _wcred })).json(); }, async codexRaces() { return (await fetch(_worldUrl('/yt_world/api/codex/races'), { credentials: _wcred })).json(); }, };