官网首页:去掉 YUSEN 小标、右上角登录/注册、进入游戏走登录闸、补 favicon;补充 game_base 菜单图标与 yt_world 沙盘记忆模块
This commit is contained in:
parent
63449893ca
commit
3d34fbc7cc
@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ============ 顶层菜单 ============ -->
|
||||
<menuitem id="menu_game_root" name="宇森游戏" sequence="10"/>
|
||||
<!-- ============ 顶层菜单(带品牌 logo) ============ -->
|
||||
<menuitem id="menu_game_root" name="宇森游戏" sequence="10"
|
||||
web_icon="game_base,static/description/icon.png"/>
|
||||
|
||||
<!-- ============ 图鉴(按分类 + 子级分组) ============ -->
|
||||
<record id="action_game_codex" model="ir.actions.act_window">
|
||||
|
||||
2
addons/yt_world/__init__.py
Normal file
2
addons/yt_world/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import models
|
||||
from . import controllers
|
||||
14
addons/yt_world/__manifest__.py
Normal file
14
addons/yt_world/__manifest__.py
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
'name': '宇森世界 - 沙盘与微观世界',
|
||||
'version': '0.2',
|
||||
'summary': '地表存储(沙盘记忆) + 微观世界(1:1预留) + 资源(框架)',
|
||||
'description': '每个玩家拥有一个确定性生成的世界;仅持久化玩家改过的地表地块(种子+增量覆盖)。微观世界独立为 1:1 模型仅预留,资源模型为框架。',
|
||||
'category': 'Game',
|
||||
'depends': ['base'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/yt_world_views.xml',
|
||||
],
|
||||
'application': True,
|
||||
'license': 'AGPL-3',
|
||||
}
|
||||
1
addons/yt_world/controllers/__init__.py
Normal file
1
addons/yt_world/controllers/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import world_api
|
||||
61
addons/yt_world/controllers/world_api.py
Normal file
61
addons/yt_world/controllers/world_api.py
Normal file
@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
from odoo import http
|
||||
from odoo.http import request, Response
|
||||
|
||||
|
||||
def _json(data):
|
||||
"""返回 UTF-8 JSON 响应(与 game_base 一致)。"""
|
||||
return Response(
|
||||
json.dumps(data, ensure_ascii=False),
|
||||
content_type='application/json; charset=utf-8',
|
||||
)
|
||||
|
||||
|
||||
def _post_body():
|
||||
try:
|
||||
raw = request.httprequest.get_data(as_text=True) or '{}'
|
||||
return json.loads(raw)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
class YtWorldApi(http.Controller):
|
||||
"""前端沙盘记忆接口(JSON over HTTP)。auth=user:需已登录 Odoo。
|
||||
前端统一带 ?db=game 且 credentials:'same-origin',故路由须为 type='http'
|
||||
并以 _json 返回原始 JSON(与 game-api.js 的 fetch().json() 对齐)。"""
|
||||
|
||||
@http.route('/yt_world/api/world', type='http', auth='user',
|
||||
methods=['GET', 'OPTIONS'], csrf=False)
|
||||
def world(self, **kw):
|
||||
"""取或建当前用户世界,导出 seed + 地块覆盖。"""
|
||||
user = request.env.user
|
||||
rec = request.env['yt.world'].sudo().get_or_create_for_user(user)
|
||||
return _json(rec.export_state())
|
||||
|
||||
@http.route('/yt_world/api/tiles/save', type='http', auth='user',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
def save_tiles(self, **kw):
|
||||
"""增量覆盖保存地块。body: {"tiles": [...]}"""
|
||||
user = request.env.user
|
||||
world = request.env['yt.world'].sudo().search(
|
||||
[('user_id', '=', user.id)], limit=1)
|
||||
if not world:
|
||||
return _json({'ok': False, 'error': 'no_world'})
|
||||
body = _post_body()
|
||||
tiles = body.get('tiles') or []
|
||||
world.import_tiles(tiles)
|
||||
return _json({'ok': True, 'saved': len(tiles)})
|
||||
|
||||
@http.route('/yt_world/api/settings', type='http', auth='user',
|
||||
methods=['POST', 'OPTIONS'], csrf=False)
|
||||
def save_settings(self, **kw):
|
||||
"""保存相机/UI 偏好。body: {"settings": {...}}"""
|
||||
user = request.env.user
|
||||
world = request.env['yt.world'].sudo().search(
|
||||
[('user_id', '=', user.id)], limit=1)
|
||||
body = _post_body()
|
||||
settings = body.get('settings')
|
||||
if world and settings is not None:
|
||||
world.settings = settings
|
||||
return _json({'ok': True})
|
||||
4
addons/yt_world/models/__init__.py
Normal file
4
addons/yt_world/models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from . import yt_world
|
||||
from . import yt_world_tile
|
||||
from . import yt_world_tile_micro
|
||||
from . import yt_resource
|
||||
15
addons/yt_world/models/constants.py
Normal file
15
addons/yt_world/models/constants.py
Normal file
@ -0,0 +1,15 @@
|
||||
TERRAIN_SELECTION = [
|
||||
('water', '水域'),
|
||||
('desert', '荒漠'),
|
||||
('plain', '平原'),
|
||||
('forest', '森林'),
|
||||
('mountain', '山脉'),
|
||||
('snow', '雪地'),
|
||||
]
|
||||
|
||||
RESOURCE_TYPE_SELECTION = [
|
||||
('wood', '木材'),
|
||||
('food', '食物'),
|
||||
('stone', '石料'),
|
||||
('fish', '鱼获'),
|
||||
]
|
||||
20
addons/yt_world/models/yt_resource.py
Normal file
20
addons/yt_world/models/yt_resource.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields
|
||||
from .constants import RESOURCE_TYPE_SELECTION
|
||||
|
||||
|
||||
class YtResource(models.Model):
|
||||
"""资源(框架模型)。本期仅结构,不接入玩法产出/消耗。"""
|
||||
_name = 'yt.resource'
|
||||
_description = '资源(框架)'
|
||||
|
||||
tile_id = fields.Many2one('yt.world.tile', string='所属地块', ondelete='cascade')
|
||||
micro_id = fields.Many2one('yt.world.tile.micro', string='所属微观世界', ondelete='cascade')
|
||||
type = fields.Selection(RESOURCE_TYPE_SELECTION, string='资源类型')
|
||||
amount = fields.Float(string='数量', default=0.0)
|
||||
capacity = fields.Float(string='容量上限', default=100.0) # [PLACEHOLDER] 调参
|
||||
|
||||
_sql_constraints = [
|
||||
('uniq_res_tile', 'unique(tile_id, type)',
|
||||
'同地块同类型资源唯一'),
|
||||
]
|
||||
88
addons/yt_world/models/yt_world.py
Normal file
88
addons/yt_world/models/yt_world.py
Normal file
@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields
|
||||
import random
|
||||
|
||||
|
||||
class YtWorld(models.Model):
|
||||
"""玩家世界:每个用户一条。seed 再生基础地图,tile_ids 存被改地块。"""
|
||||
_name = 'yt.world'
|
||||
_description = '玩家世界'
|
||||
|
||||
name = fields.Char(string='世界名称', translate=True)
|
||||
user_id = fields.Many2one('res.users', string='拥有者',
|
||||
required=True, ondelete='cascade', index=True)
|
||||
seed = fields.Integer(string='生成种子', required=True, index=True)
|
||||
world_version = fields.Integer(string='生成算法版本', default=1)
|
||||
settings = fields.Json(string='世界设置') # 相机位/缩放/UI 偏好
|
||||
tile_ids = fields.One2many('yt.world.tile', 'world_id', string='地表地块')
|
||||
created_at = fields.Datetime(string='创建时间', default=fields.Datetime.now)
|
||||
last_active = fields.Datetime(string='最后活跃')
|
||||
|
||||
# ---------- 接口方法(供 controller 调用) ----------
|
||||
|
||||
def get_or_create_for_user(self, user):
|
||||
"""取当前用户的世界;没有则按随机 seed 新建。"""
|
||||
rec = self.search([('user_id', '=', user.id)], limit=1)
|
||||
if not rec:
|
||||
rec = self.create({
|
||||
'user_id': user.id,
|
||||
'seed': random.randint(1, 2_000_000_000),
|
||||
'name': '我的世界',
|
||||
})
|
||||
rec.last_active = fields.Datetime.now()
|
||||
return rec
|
||||
|
||||
def export_state(self):
|
||||
"""导出前端需要的世界状态:seed + 地块覆盖列表。"""
|
||||
self.ensure_one()
|
||||
tiles = []
|
||||
for t in self.tile_ids:
|
||||
tiles.append({
|
||||
'q': t.q,
|
||||
'r': t.r,
|
||||
'terrain': t.terrain or None,
|
||||
'height': t.height or None,
|
||||
'flags': t.flags or {},
|
||||
'micro_world_id': t.micro_world_id.id if t.micro_world_id else None,
|
||||
})
|
||||
return {
|
||||
'world_id': self.id,
|
||||
'seed': self.seed,
|
||||
'world_version': self.world_version,
|
||||
'settings': self.settings or {},
|
||||
'tiles': tiles,
|
||||
}
|
||||
|
||||
def import_tiles(self, tiles):
|
||||
"""增量覆盖:upsert 地块。terrain/height/flags 全回退底图则删除覆盖行。"""
|
||||
self.ensure_one()
|
||||
tile_model = self.env['yt.world.tile']
|
||||
for tv in (tiles or []):
|
||||
q, r = tv.get('q'), tv.get('r')
|
||||
if q is None or r is None:
|
||||
continue
|
||||
terrain = tv.get('terrain')
|
||||
height = tv.get('height')
|
||||
flags = tv.get('flags')
|
||||
# 全部回退底图 → 删覆盖行,保持最小集(种子+增量覆盖原则)
|
||||
if not terrain and height is None and not (flags or {}):
|
||||
existing = tile_model.search([
|
||||
('world_id', '=', self.id), ('q', '=', q), ('r', '=', r)])
|
||||
if existing:
|
||||
existing.unlink()
|
||||
continue
|
||||
vals = {}
|
||||
if terrain is not None:
|
||||
vals['terrain'] = terrain
|
||||
if height is not None:
|
||||
vals['height'] = height
|
||||
if flags is not None:
|
||||
vals['flags'] = flags
|
||||
existing = tile_model.search([
|
||||
('world_id', '=', self.id), ('q', '=', q), ('r', '=', r)])
|
||||
if existing:
|
||||
existing.write(vals)
|
||||
else:
|
||||
vals.update({'q': q, 'r': r, 'world_id': self.id})
|
||||
tile_model.create(vals) # create 钩子自动建微观占位
|
||||
self.last_active = fields.Datetime.now()
|
||||
52
addons/yt_world/models/yt_world_tile.py
Normal file
52
addons/yt_world/models/yt_world_tile.py
Normal file
@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from .constants import TERRAIN_SELECTION
|
||||
|
||||
|
||||
class YtWorldTile(models.Model):
|
||||
"""地表地块:本期实际持久化内容(坐标 + 高度 + 属性 + 微观世界关联位)。"""
|
||||
_name = 'yt.world.tile'
|
||||
_description = '地表地块'
|
||||
_rec_name = 'coord'
|
||||
|
||||
world_id = fields.Many2one('yt.world', string='所属世界',
|
||||
required=True, ondelete='cascade')
|
||||
q = fields.Integer(string='轴向坐标Q', required=True)
|
||||
r = fields.Integer(string='轴向坐标R', required=True)
|
||||
terrain = fields.Selection(TERRAIN_SELECTION, string='地形', index=True)
|
||||
height = fields.Integer(string='高度等级') # heightLevel 1-12
|
||||
flags = fields.Json(string='地表标记') # {claimed, decorated}
|
||||
micro_world_id = fields.Many2one('yt.world.tile.micro',
|
||||
string='微观世界', ondelete='set null')
|
||||
updated_at = fields.Datetime(string='更新时间', default=fields.Datetime.now)
|
||||
coord = fields.Char(string='坐标标识', compute='_compute_coord', store=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('uniq_tile', 'unique(world_id, q, r)',
|
||||
'同一世界内 (坐标) 的地块必须唯一'),
|
||||
]
|
||||
|
||||
@api.depends('q', 'r')
|
||||
def _compute_coord(self):
|
||||
for t in self:
|
||||
t.coord = "%s,%s" % (t.q, t.r)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
rec = super(YtWorldTile, self).create(vals)
|
||||
rec._ensure_micro()
|
||||
return rec
|
||||
|
||||
def _ensure_micro(self):
|
||||
"""新建地块时同步建 1:1 微观世界占位。"""
|
||||
for t in self:
|
||||
if not t.micro_world_id:
|
||||
m = self.env['yt.world.tile.micro'].create({'tile_id': t.id})
|
||||
t.micro_world_id = m
|
||||
|
||||
@api.model
|
||||
def write(self, vals):
|
||||
# 任何编辑都刷新 updated_at
|
||||
if 'updated_at' not in vals:
|
||||
vals['updated_at'] = fields.Datetime.now()
|
||||
return super(YtWorldTile, self).write(vals)
|
||||
22
addons/yt_world/models/yt_world_tile_micro.py
Normal file
22
addons/yt_world/models/yt_world_tile_micro.py
Normal file
@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class YtWorldTileMicro(models.Model):
|
||||
"""微观世界(预留 1:1 模型)。当前仅骨架,内容与资源后续开发。"""
|
||||
_name = 'yt.world.tile.micro'
|
||||
_description = '微观世界(预留)'
|
||||
|
||||
tile_id = fields.Many2one('yt.world.tile', string='所属地块',
|
||||
required=True, ondelete='cascade')
|
||||
name = fields.Char(string='微观世界名称', translate=True)
|
||||
# ===== 预留字段(后续放开,本期不实现) =====
|
||||
# people = fields.Json(string='居民') # [{id, name, job, mood, x, y}]
|
||||
# buildings = fields.Json(string='建筑') # [{type, level, x, y, rot}]
|
||||
# resources = fields.Json(string='资源') # {wood, food, stone, fish}
|
||||
# ===========================================
|
||||
|
||||
_sql_constraints = [
|
||||
('uniq_micro', 'unique(tile_id)',
|
||||
'一个地块只能有一个微观世界(一对一)'),
|
||||
]
|
||||
5
addons/yt_world/security/ir.model.access.csv
Normal file
5
addons/yt_world/security/ir.model.access.csv
Normal file
@ -0,0 +1,5 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_yt_world,yt.world,model_yt_world,base.group_user,1,1,1,1
|
||||
access_yt_world_tile,yt.world.tile,model_yt_world_tile,base.group_user,1,1,1,1
|
||||
access_yt_world_tile_micro,yt.world.tile.micro,model_yt_world_tile_micro,base.group_user,1,1,1,1
|
||||
access_yt_resource,yt.resource,model_yt_resource,base.group_user,1,1,1,1
|
||||
|
BIN
addons/yt_world/static/description/icon.png
Normal file
BIN
addons/yt_world/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
111
addons/yt_world/views/yt_world_views.xml
Normal file
111
addons/yt_world/views/yt_world_views.xml
Normal file
@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ============ 顶层菜单(带品牌 logo,直接打开玩家世界列表) ============ -->
|
||||
<menuitem id="menu_yt_world_root"
|
||||
name="宇森世界"
|
||||
sequence="20"
|
||||
action="action_yt_world"
|
||||
web_icon="yt_world,static/description/icon.png"/>
|
||||
|
||||
<!-- ============ 玩家世界 ============ -->
|
||||
<record id="action_yt_world" model="ir.actions.act_window">
|
||||
<field name="name">玩家世界</field>
|
||||
<field name="res_model">yt.world</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<record id="view_yt_world_list" model="ir.ui.view">
|
||||
<field name="name">yt.world.list</field>
|
||||
<field name="model">yt.world</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="seed"/>
|
||||
<field name="world_version"/>
|
||||
<field name="last_active"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_yt_world_form" model="ir.ui.view">
|
||||
<field name="name">yt.world.form</field>
|
||||
<field name="model">yt.world</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
<field name="seed"/>
|
||||
<field name="world_version"/>
|
||||
<field name="settings" widget="json"/>
|
||||
<field name="last_active"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="地表地块">
|
||||
<field name="tile_ids">
|
||||
<list>
|
||||
<field name="q"/>
|
||||
<field name="r"/>
|
||||
<field name="terrain"/>
|
||||
<field name="height"/>
|
||||
<field name="micro_world_id"/>
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============ 地表地块 ============ -->
|
||||
<menuitem id="menu_yt_world_tile" name="地表地块"
|
||||
parent="menu_yt_world_root" action="action_yt_world_tile" sequence="10"/>
|
||||
|
||||
<record id="action_yt_world_tile" model="ir.actions.act_window">
|
||||
<field name="name">地表地块</field>
|
||||
<field name="res_model">yt.world.tile</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<record id="view_yt_world_tile_list" model="ir.ui.view">
|
||||
<field name="name">yt.world.tile.list</field>
|
||||
<field name="model">yt.world.tile</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="world_id"/>
|
||||
<field name="q"/>
|
||||
<field name="r"/>
|
||||
<field name="terrain"/>
|
||||
<field name="height"/>
|
||||
<field name="micro_world_id"/>
|
||||
<field name="updated_at"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_yt_world_tile_form" model="ir.ui.view">
|
||||
<field name="name">yt.world.tile.form</field>
|
||||
<field name="model">yt.world.tile</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="world_id"/>
|
||||
<field name="q"/>
|
||||
<field name="r"/>
|
||||
<field name="terrain"/>
|
||||
<field name="height"/>
|
||||
<field name="flags" widget="json"/>
|
||||
<field name="micro_world_id"/>
|
||||
<field name="updated_at"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@ -39,4 +39,22 @@ const API = {
|
||||
async announcements() {
|
||||
return (await fetch('/game/api/announcements?db=game', { credentials: 'same-origin' })).json();
|
||||
},
|
||||
// ---------- 沙盘记忆(yt_world 模块) ----------
|
||||
async world() {
|
||||
return (await fetch('/yt_world/api/world?db=game', { credentials: 'same-origin' })).json();
|
||||
},
|
||||
async saveTiles(tiles) {
|
||||
return (await fetch('/yt_world/api/tiles/save?db=game', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ tiles }),
|
||||
})).json();
|
||||
},
|
||||
async saveSettings(settings) {
|
||||
return (await fetch('/yt_world/api/settings?db=game', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings }),
|
||||
})).json();
|
||||
},
|
||||
};
|
||||
|
||||
60
index.html
60
index.html
@ -7,6 +7,9 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=ZCOOL+QingKe+HuangYou&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="assets/ui/logo_planet_48.png?v=20260723">
|
||||
<link rel="icon" type="image/png" sizes="256x256" href="assets/ui/logo_planet_256.png?v=20260723">
|
||||
<link rel="apple-touch-icon" href="assets/ui/logo_planet_256.png?v=20260723">
|
||||
<style>
|
||||
:root{
|
||||
--bg-0:#080c1a; --bg-1:#0e1730; --bg-2:#15213f;
|
||||
@ -45,6 +48,10 @@
|
||||
.user-box .ub-name{color:#fff;font-weight:500;}
|
||||
.user-box .ub-logout{font-family:inherit;font-size:12px;color:var(--text-dim);background:rgba(255,255,255,.06);border:1px solid var(--card-bd);padding:5px 12px;border-radius:8px;cursor:pointer;transition:.2s;}
|
||||
.user-box .ub-logout:hover{color:#fff;background:rgba(139,92,246,.18);}
|
||||
.user-box .auth-link{color:var(--text-dim);text-decoration:none;font-size:14px;transition:.2s;padding:5px 8px;}
|
||||
.user-box .auth-link:hover{color:#fff;}
|
||||
.user-box .auth-btn{font-family:inherit;font-size:13px;color:#0b1020;background:linear-gradient(120deg,var(--gold),#f0d49a);border:none;padding:6px 16px;border-radius:20px;cursor:pointer;font-weight:600;transition:.2s;}
|
||||
.user-box .auth-btn:hover{filter:brightness(1.1);box-shadow:0 0 16px rgba(231,184,92,.4);}
|
||||
.cta:hover{filter:brightness(1.08);}
|
||||
|
||||
.content{flex:1;position:relative;overflow:hidden;}
|
||||
@ -178,7 +185,7 @@
|
||||
<body>
|
||||
<div class="app">
|
||||
<header class="topbar">
|
||||
<div class="logo"><span class="cn">宇森</span><span class="en">YUSEN</span></div>
|
||||
<div class="logo"><span class="cn">宇森</span></div>
|
||||
<nav class="nav">
|
||||
<button data-tab="intro" class="active">介绍</button>
|
||||
<button data-tab="codex">图鉴</button>
|
||||
@ -540,31 +547,44 @@
|
||||
// ============ 登录态 / 右上角个人信息(从 Odoo 取) ============
|
||||
(function(){
|
||||
const userBox = document.getElementById('userBox');
|
||||
async function refreshUserBox(){
|
||||
let cachedMe = null;
|
||||
|
||||
function renderLoggedOut(){
|
||||
if(!userBox) return;
|
||||
userBox.innerHTML = '<a href="login.html?next=world.html" class="auth-link">登录</a><a href="login.html?next=world.html" class="auth-btn">注册</a>';
|
||||
}
|
||||
|
||||
function renderLoggedIn(me){
|
||||
if(!userBox) return;
|
||||
userBox.innerHTML = '<span class="ub-name"></span><button class="ub-logout" id="ubLogout">退出</button>';
|
||||
const nm = userBox.querySelector('.ub-name');
|
||||
if(nm) nm.textContent = me.name || me.login;
|
||||
const lb = document.getElementById('ubLogout');
|
||||
if(lb) lb.addEventListener('click', async ()=>{
|
||||
try { await API.logout(); } catch(e){}
|
||||
location.href = 'index.html';
|
||||
});
|
||||
}
|
||||
|
||||
async function refreshUserBox(){
|
||||
try{
|
||||
const me = await API.me();
|
||||
if(me && me.uid){
|
||||
userBox.innerHTML = '<span class="ub-name"></span><button class="ub-logout" id="ubLogout">退出</button>';
|
||||
const nm = userBox.querySelector('.ub-name');
|
||||
if(nm) nm.textContent = me.name || me.login;
|
||||
const lb = document.getElementById('ubLogout');
|
||||
if(lb) lb.addEventListener('click', async ()=>{
|
||||
await API.logout();
|
||||
location.href = 'index.html';
|
||||
});
|
||||
} else {
|
||||
userBox.innerHTML = ''; // 未登录:右上角不显示登录入口,登录经“进入游戏”引导
|
||||
}
|
||||
}catch(e){ userBox.innerHTML = ''; }
|
||||
cachedMe = await API.me();
|
||||
if(cachedMe && cachedMe.uid) renderLoggedIn(cachedMe);
|
||||
else renderLoggedOut();
|
||||
}catch(e){ renderLoggedOut(); }
|
||||
}
|
||||
refreshUserBox();
|
||||
|
||||
// ============ 进入游戏:已登录→世界页;未登录→提示登录 ============
|
||||
// TEMP: 本地临时默认直接进入游戏,跳过 Odoo 登录校验;打通后端后改回 API.me() 判断
|
||||
// ============ 进入游戏:已登录→世界页;未登录→登录页 ============
|
||||
const enterBtn = document.getElementById('enterGame');
|
||||
if(enterBtn) enterBtn.addEventListener('click', ()=>{
|
||||
location.href = 'world.html';
|
||||
if(enterBtn) enterBtn.addEventListener('click', async ()=>{
|
||||
try {
|
||||
const me = cachedMe || await API.me();
|
||||
if (me && me.uid) location.href = 'world.html';
|
||||
else location.href = 'login.html?next=world.html';
|
||||
} catch(e) {
|
||||
location.href = 'login.html?next=world.html';
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user