- game_base: 新增 game.page/page.item 模型(page.py),密码策略加严(>=8位且含字母+数字) - yt_world: create 改 create_multi;权限扩至 public/portal/user;新增微观世界/资源视图与菜单 - 前端: website/auth/index 等 ?db=yt_game 统一改为 ?db=game(线上库名) - 新增 deploy_server.sh 一键部署脚本
232 lines
10 KiB
HTML
232 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<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:100vh;overflow:hidden;position:relative;
|
||
}
|
||
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{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);}
|
||
|
||
.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;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<div class="brand">
|
||
<div class="cn">宇森</div>
|
||
<div class="en">YUSEN · GARDEN OF CIVILIZATION</div>
|
||
<div class="tag">你不是玩家,你是园丁。</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>
|
||
<input id="rName" type="text" placeholder="默认与账号一致,可改" />
|
||
</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 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;
|
||
});
|
||
|
||
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>
|