fix: 注册页昵称随机生成 + 去园丁文案 + 后端校验昵称重复

- login.html: 移除副标题'你不是玩家,你是园丁。'
- login.html: 昵称输入框增加随机按钮(🎲),点击生成随机昵称
- login.html: 账号输入时不再自动同步昵称
- login.html: 提交按钮'创建园丁账号'改为'创建账号'
- game_api.py: 注册时校验 res.users.name 重复,返回'该昵称已被使用'
- assets/game-api.js: 沙盘/世界数据接口按本地/线上自适应 WORLD_DB
This commit is contained in:
李鹏宇 2026-07-24 21:23:10 +08:00
parent 146da5e8e4
commit f12cab0b53
3 changed files with 33 additions and 21 deletions

View File

@ -297,6 +297,8 @@ class GameApiController(http.Controller):
User = request.env['res.users'].sudo()
if User.search([('login', '=', login)]):
return _json({'ok': False, 'error': '该账号已存在'})
if name and User.search([('name', '=', name)]):
return _json({'ok': False, 'error': '该昵称已被使用'})
try:
portal = request.env.ref('base.group_portal')
user = User.create({

View File

@ -1,8 +1,8 @@
// 宇森游戏 · 前端与 Odoo 通信层
// 本地调试:前端 8888 -> Odoo 8069跨域带调试 CORS 头)
// 生产部署:前端与 Odoo 同域,由 nginx 代理 /game/*。
// 说明odoo.conf 已 db_name=yt_game 钉死库URL 上的 ?db= 形同虚设,
// 请求一律落到 yt_game下列 game/* 沿用统一 API_CONFIGyt_world/* 单独带 ?db=yt_game
// 库名说明:本地 Odoo 用 odoo.conf 钉死 yt_game线上游戏库名为 gamedbfilter 不含 yt_game
// game/* 走统一 API_CONFIG.dbyt_world/* 单独带 ?db=,按 WORLD_DB 区分本地/线上,避免 git 同步互相覆盖
const API_CONFIG = {
base: (location.hostname === '127.0.0.1' && location.port === '8888')
@ -13,6 +13,9 @@ const API_CONFIG = {
db: (location.hostname === '127.0.0.1' && location.port === '8888') ? 'study' : 'game',
};
// 世界(yt_world)所在库:本地 Odoo 钉 yt_game线上游戏库名为 game。按域名自适应git 同步不冲突。
const WORLD_DB = (location.hostname === '127.0.0.1' || location.hostname === 'localhost') ? 'yt_game' : 'game';
const _cred = API_CONFIG.base ? 'include' : 'same-origin';
function _url(path) {
@ -68,17 +71,17 @@ const API = {
},
// ---------- 沙盘记忆yt_world 模块) ----------
async world() {
return (await fetch('/yt_world/api/world?db=yt_game', { credentials: 'same-origin' })).json();
return (await fetch('/yt_world/api/world?db=' + WORLD_DB, { credentials: 'same-origin' })).json();
},
async saveTiles(tiles) {
return (await fetch('/yt_world/api/tiles/save?db=yt_game', {
return (await fetch('/yt_world/api/tiles/save?db=' + WORLD_DB, {
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=yt_game', {
return (await fetch('/yt_world/api/settings?db=' + WORLD_DB, {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ settings }),
@ -87,14 +90,14 @@ const API = {
// ---------- 世界数据 / 包裹后端接口预留yt_world 暂未实现路由,前端 mock 兜底) ----------
async worldData() {
try {
const r = await fetch('/yt_world/api/data?db=yt_game', { credentials: 'same-origin' });
const r = await fetch('/yt_world/api/data?db=' + WORLD_DB, { credentials: 'same-origin' });
if (!r.ok) return null;
return await r.json();
} catch (e) { return null; }
},
async inventory() {
try {
const r = await fetch('/yt_world/api/inventory?db=yt_game', { credentials: 'same-origin' });
const r = await fetch('/yt_world/api/inventory?db=' + WORLD_DB, { credentials: 'same-origin' });
if (!r.ok) return null;
return await r.json();
} catch (e) { return null; }

View File

@ -45,8 +45,9 @@
.field input:focus{border-color:var(--violet);box-shadow:0 0 0 3px rgba(139,92,246,.18);}
.pw-wrap{position:relative;}
.pw-wrap input{padding-right:40px;}
.pw-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:transparent;border:none;color:var(--text-dim);cursor:pointer;font-size:16px;line-height:1;padding:4px 6px;border-radius:6px;transition:.2s;}
.pw-toggle:hover{color:#fff;background:rgba(255,255,255,.08);}
.pw-toggle,.random-btn{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:transparent;border:none;color:var(--text-dim);cursor:pointer;font-size:16px;line-height:1;padding:4px 6px;border-radius:6px;transition:.2s;}
.pw-toggle:hover,.random-btn:hover{color:#fff;background:rgba(255,255,255,.08);}
.random-btn{font-size:15px;}
.submit{width:100%;margin-top:6px;font-size:15px;font-weight:700;color:#0b1020;background:linear-gradient(120deg,var(--gold),#f0d49a);padding:12px;border-radius:11px;cursor:pointer;border:none;letter-spacing:2px;transition:.22s;box-shadow:0 0 18px rgba(231,184,92,.35);}
.submit:hover{filter:brightness(1.08);}
@ -75,7 +76,6 @@
<div class="brand">
<div class="cn">宇森</div>
<div class="en">YUSEN · GARDEN OF CIVILIZATION</div>
<div class="tag">你不是玩家,你是园丁。</div>
</div>
<div class="card">
@ -108,7 +108,10 @@
</div>
<div class="field">
<label>昵称</label>
<input id="rName" type="text" placeholder="默认与账号一致,可改" />
<div class="pw-wrap">
<input id="rName" type="text" placeholder="可随机生成,也可自定义" />
<button type="button" class="random-btn" id="randomNameBtn" aria-label="随机昵称">🎲</button>
</div>
</div>
<div class="field">
<label>密码</label>
@ -124,7 +127,7 @@
<button type="button" class="pw-toggle" data-target="rPwd2" aria-label="显示密码">👁</button>
</div>
</div>
<button class="submit" type="submit">创建园丁账号</button>
<button class="submit" type="submit">创建账号</button>
</form>
<div class="msg" id="msg"></div>
@ -181,14 +184,18 @@
});
});
// 昵称默认 = 账号:账号变更时若昵称仍空/等于上次账号值,则同步;用户改过昵称则保留
const rLogin = document.getElementById('rLogin');
// 随机昵称生成
const rName = document.getElementById('rName');
let lastLoginVal = '';
rLogin.addEventListener('input', () => {
const v = rLogin.value.trim();
if (rName.value === '' || rName.value === lastLoginVal) rName.value = v;
lastLoginVal = v;
const ADJ = ['云游','星尘','微光','风吟','夜语','深海','荒原','天穹','逐风','灵叶','虚境','晨星','暮雨','浮世','苍穹'];
const NOUN = ['旅者','观察者','行者','拾光人','园丁','筑梦师','游侠','学者','守夜人','引路人','浪人','渡鸦','白鹿'];
function randomName() {
const a = ADJ[Math.floor(Math.random() * ADJ.length)];
const n = NOUN[Math.floor(Math.random() * NOUN.length)];
const num = String(Math.floor(Math.random() * 9000) + 1000);
return a + n + num;
}
document.getElementById('randomNameBtn').addEventListener('click', () => {
rName.value = randomName();
});
loginForm.addEventListener('submit', async (e) => {
@ -230,7 +237,7 @@
} catch (err) {
showMsg('网络错误,请稍后重试', 'err');
} finally {
btn.disabled = false; btn.textContent = '创建园丁账号';
btn.disabled = false; btn.textContent = '创建账号';
}
});
</script>