155 lines
5.8 KiB
JavaScript
155 lines
5.8 KiB
JavaScript
// 宇森游戏 · 前端与 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 achievements() {
|
||
return (await fetch(_url('/game/api/achievements'), { credentials: _cred })).json();
|
||
},
|
||
async mails() {
|
||
return (await fetch(_url('/game/api/mails'), { credentials: _cred })).json();
|
||
},
|
||
async mailRead(mailId) {
|
||
return (await fetch(_url('/game/api/mail/read'), {
|
||
method: 'POST', credentials: _cred,
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ mail_id: mailId }),
|
||
})).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();
|
||
},
|
||
// 暴露后端 base,供图片等静态资源 URL 拼接(本地跨域→8069;线上同域→'')
|
||
base: API_CONFIG.base,
|
||
};
|