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

246 lines
11 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>宇森 · 登录</title>
<style>
:root{
--bg-0:#080c1a; --bg-1:#0e1730; --bg-2:#15213f;
--violet:#8b5cf6; --magenta:#c77dff; --cyan:#4cc9f0; --gold:#e7b85c;
--text:#e9edf6; --text-dim:#9aa6c4;
--card:rgba(255,255,255,.05); --card-bd:rgba(255,255,255,.12);
--glow:0 0 22px rgba(139,92,246,.30);
}
*{box-sizing:border-box;margin:0;padding:0;}
html,body{height:100%;}
body{
font-family:"PingFang SC","Hiragino Sans GB","Microsoft YaHei","Noto Sans CJK SC",system-ui,-apple-system,sans-serif;color:var(--text);
background:
radial-gradient(1000px 620px at 82% -8%, rgba(139,92,246,.16), transparent 60%),
radial-gradient(820px 560px at 8% 112%, rgba(76,201,240,.12), transparent 55%),
linear-gradient(160deg, var(--bg-0), var(--bg-1) 48%, var(--bg-2));
display:flex;align-items:center;justify-content:center;min-height:100dvh;overflow:hidden;position:relative;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
body::before,body::after{content:"";position:fixed;border-radius:50%;filter:blur(80px);z-index:0;pointer-events:none;}
body::before{width:380px;height:380px;background:rgba(139,92,246,.20);top:-110px;right:-70px;animation:f1 20s ease-in-out infinite;}
body::after{width:340px;height:340px;background:rgba(76,201,240,.12);bottom:-110px;left:-60px;animation:f2 24s ease-in-out infinite;}
@keyframes f1{0%,100%{transform:translate(0,0)}50%{transform:translate(-36px,36px)}}
@keyframes f2{0%,100%{transform:translate(0,0)}50%{transform:translate(36px,-28px)}}
.wrap{position:relative;z-index:2;width:min(420px,92vw);}
.brand{text-align:center;margin-bottom:26px;}
.brand .cn{font-family:"PingFang SC","Microsoft YaHei",system-ui,sans-serif;font-weight:900;font-size:42px;letter-spacing:8px;color:#fff;text-shadow:0 0 18px rgba(139,92,246,.6);}
.brand .en{font-size:11px;color:var(--magenta);letter-spacing:3px;margin-top:6px;}
.brand .tag{font-size:13px;color:var(--text-dim);margin-top:10px;}
.card{background:var(--card);border:1px solid var(--card-bd);border-radius:18px;padding:26px 26px 30px;backdrop-filter:blur(10px);box-shadow:var(--glow);}
.tabs{display:flex;gap:6px;margin-bottom:20px;background:rgba(8,12,26,.5);border-radius:12px;padding:5px;}
.tabs button{flex:1;font-family:inherit;font-size:14px;font-weight:500;color:var(--text-dim);background:transparent;border:none;padding:9px;border-radius:9px;cursor:pointer;transition:.2s;}
.tabs button.active{color:#fff;background:rgba(139,92,246,.28);box-shadow:var(--glow);}
.field{margin-bottom:14px;}
.field label{display:block;font-size:12px;color:var(--text-dim);margin-bottom:6px;letter-spacing:1px;}
.field input{width:100%;background:rgba(8,12,26,.6);border:1px solid var(--card-bd);border-radius:10px;color:var(--text);padding:11px 13px;font-family:inherit;font-size:14px;outline:none;transition:.2s;}
.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,.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);}
.submit:disabled{opacity:.6;cursor:default;}
.msg{margin-top:14px;font-size:13px;text-align:center;min-height:18px;}
.msg.err{color:#ff8a8a;}
.msg.ok{color:#8be39a;}
.back{text-align:center;margin-top:18px;font-size:13px;color:var(--text-dim);}
.back a{color:var(--cyan);text-decoration:none;}
.back a:hover{text-decoration:underline;}
.hidden{display:none;}
@media (max-width:420px){
.brand .cn{font-size:34px;letter-spacing:5px;}
.card{padding:22px 18px 26px;}
.field input{padding:12px 13px;font-size:15px;}
.submit{padding:13px;font-size:15px;}
}
</style>
</head>
<body>
<div class="wrap">
<div class="brand">
<div class="cn">宇森</div>
<div class="en">YUSEN · GARDEN OF CIVILIZATION</div>
</div>
<div class="card">
<div class="tabs">
<button id="tabLogin" class="active">登录</button>
<button id="tabReg">注册</button>
</div>
<!-- 登录 -->
<form id="loginForm" autocomplete="off">
<div class="field">
<label>账号</label>
<input id="lLogin" type="text" placeholder="用户名" />
</div>
<div class="field">
<label>密码</label>
<div class="pw-wrap">
<input id="lPwd" type="password" placeholder="密码" />
<button type="button" class="pw-toggle" data-target="lPwd" aria-label="显示密码">👁</button>
</div>
</div>
<button class="submit" type="submit">进入世界</button>
</form>
<!-- 注册 -->
<form id="regForm" class="hidden" autocomplete="off">
<div class="field">
<label>账号</label>
<input id="rLogin" type="text" placeholder="登录用账号(字母/数字/._-" />
</div>
<div class="field">
<label>昵称</label>
<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>
<div class="pw-wrap">
<input id="rPwd" type="password" placeholder="至少 8 位,字母+数字" />
<button type="button" class="pw-toggle" data-target="rPwd" aria-label="显示密码">👁</button>
</div>
</div>
<div class="field">
<label>确认密码</label>
<div class="pw-wrap">
<input id="rPwd2" type="password" placeholder="再次输入密码" />
<button type="button" class="pw-toggle" data-target="rPwd2" aria-label="显示密码">👁</button>
</div>
</div>
<button class="submit" type="submit">创建账号</button>
</form>
<div class="msg" id="msg"></div>
</div>
<div class="back"><a href="index.html">← 返回官网</a></div>
</div>
<script src="assets/game-api.js"></script>
<script>
const tabLogin = document.getElementById('tabLogin');
const tabReg = document.getElementById('tabReg');
const loginForm = document.getElementById('loginForm');
const regForm = document.getElementById('regForm');
const msg = document.getElementById('msg');
// 回跳目标(进入游戏流程带 ?next=world.html
const params = new URLSearchParams(location.search);
const nextUrl = params.get('next') || 'world.html';
// 已登录则直接跳走
(async () => {
try { const me = await API.me(); if (me && me.uid) location.href = nextUrl; } catch (e) {}
})();
function showMsg(text, kind){ msg.textContent = text || ''; msg.className = 'msg' + (kind ? ' ' + kind : ''); }
function switchTab(which){
if (which === 'login'){
tabLogin.classList.add('active'); tabReg.classList.remove('active');
loginForm.classList.remove('hidden'); regForm.classList.add('hidden');
} else {
tabReg.classList.add('active'); tabLogin.classList.remove('active');
regForm.classList.remove('hidden'); loginForm.classList.add('hidden');
}
showMsg('');
}
tabLogin.onclick = () => switchTab('login');
tabReg.onclick = () => switchTab('reg');
// 密码显示/隐藏切换(眼睛按钮)
document.querySelectorAll('.pw-toggle').forEach(btn => {
btn.addEventListener('click', () => {
const input = document.getElementById(btn.dataset.target);
if (!input) return;
if (input.type === 'password') {
input.type = 'text';
btn.textContent = '🙈';
btn.setAttribute('aria-label', '隐藏密码');
} else {
input.type = 'password';
btn.textContent = '👁';
btn.setAttribute('aria-label', '显示密码');
}
input.focus();
});
});
// 随机昵称生成
const rName = document.getElementById('rName');
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) => {
e.preventDefault();
showMsg('');
const login = document.getElementById('lLogin').value.trim();
const password = document.getElementById('lPwd').value;
if (!login || !password){ showMsg('请填写账号和密码', 'err'); return; }
const btn = loginForm.querySelector('.submit'); btn.disabled = true; btn.textContent = '登录中…';
try {
const r = await API.login(login, password);
if (r.ok){ location.href = nextUrl; }
else { showMsg(r.error || '登录失败', 'err'); }
} catch (err) {
showMsg('网络错误,请稍后重试', 'err');
} finally {
btn.disabled = false; btn.textContent = '进入世界';
}
});
regForm.addEventListener('submit', async (e) => {
e.preventDefault();
showMsg('');
const login = document.getElementById('rLogin').value.trim();
const name = document.getElementById('rName').value.trim();
const password = document.getElementById('rPwd').value;
const pwd2 = document.getElementById('rPwd2').value;
if (!login) { showMsg('请填写账号', 'err'); return; }
if (password.length < 8) { showMsg('密码至少 8 位', 'err'); return; }
if (!/[a-zA-Z]/.test(password) || !/\d/.test(password)) {
showMsg('密码必须同时包含字母和数字', 'err'); return;
}
if (password !== pwd2) { showMsg('两次密码输入不一致', 'err'); return; }
const btn = regForm.querySelector('.submit'); btn.disabled = true; btn.textContent = '注册中…';
try {
const r = await API.register({ name, login, password });
if (r.ok){ location.href = nextUrl; }
else { showMsg(r.error || '注册失败', 'err'); }
} catch (err) {
showMsg('网络错误,请稍后重试', 'err');
} finally {
btn.disabled = false; btn.textContent = '创建账号';
}
});
</script>
</body>
</html>