feat: Odoo login integration for all pages
This commit is contained in:
parent
424923c81e
commit
d01703dd3f
146
website/auth.css
Normal file
146
website/auth.css
Normal file
@ -0,0 +1,146 @@
|
||||
/* 宇森 - 登录认证样式 */
|
||||
|
||||
/* ---- Nav bar auth area ---- */
|
||||
#auth-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.auth-btn {
|
||||
background: rgba(100, 200, 255, 0.15);
|
||||
border: 1px solid rgba(100, 200, 255, 0.35);
|
||||
color: #c8e6ff;
|
||||
padding: 6px 16px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
.auth-btn:hover {
|
||||
background: rgba(100, 200, 255, 0.25);
|
||||
border-color: rgba(100, 200, 255, 0.6);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.login-btn {}
|
||||
.logout-btn {
|
||||
background: rgba(255, 100, 100, 0.12);
|
||||
border-color: rgba(255, 100, 100, 0.3);
|
||||
color: #fcc;
|
||||
}
|
||||
.logout-btn:hover {
|
||||
background: rgba(255, 100, 100, 0.22);
|
||||
border-color: rgba(255, 100, 100, 0.5);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
color: #a8e6cf;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ---- Login Modal ---- */
|
||||
#login-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.login-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(5, 8, 16, 0.8);
|
||||
backdrop-filter: blur(6px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-box {
|
||||
background: linear-gradient(135deg, rgba(15, 23, 42, 0.95), rgba(10, 18, 35, 0.98));
|
||||
border: 1px solid rgba(100, 200, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 36px 32px;
|
||||
width: 360px;
|
||||
max-width: 90vw;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 80px rgba(100, 200, 255, 0.08);
|
||||
}
|
||||
|
||||
.login-box h2 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 22px;
|
||||
background: linear-gradient(90deg, #4af, #a8e6cf);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.login-sub {
|
||||
margin: 0 0 24px;
|
||||
color: #6a8;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.login-box input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 14px;
|
||||
margin-bottom: 12px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid rgba(100, 200, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
color: #e0e8f0;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: border 0.2s;
|
||||
}
|
||||
.login-box input:focus {
|
||||
border-color: rgba(100, 200, 255, 0.5);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.login-error {
|
||||
color: #f66;
|
||||
font-size: 12px;
|
||||
min-height: 18px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.login-submit {
|
||||
width: 100%;
|
||||
padding: 11px;
|
||||
margin-top: 4px;
|
||||
background: linear-gradient(135deg, #2a6, #4af);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
.login-submit:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 20px rgba(68, 170, 255, 0.3);
|
||||
}
|
||||
.login-submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
margin: 16px 0 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #557;
|
||||
}
|
||||
.login-footer a {
|
||||
color: #4af;
|
||||
text-decoration: none;
|
||||
}
|
||||
.login-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
188
website/auth.js
Normal file
188
website/auth.js
Normal file
@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 宇森 - Odoo 用户认证模块
|
||||
* 通过 Odoo JSON-RPC 实现登录/登出,与 Odoo 用户系统打通
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const AUTH_URL = '/web/session/authenticate';
|
||||
const DESTROY_URL = '/web/session/destroy';
|
||||
const SESSION_URL = '/web/session/get_session_info';
|
||||
|
||||
// ============ State ============
|
||||
let currentUser = null;
|
||||
|
||||
// ============ Init ============
|
||||
async function init() {
|
||||
try {
|
||||
const resp = await fetch(SESSION_URL, { credentials: 'same-origin' });
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
if (data.result && data.result.uid) {
|
||||
currentUser = {
|
||||
uid: data.result.uid,
|
||||
username: data.result.username,
|
||||
name: data.result.name || data.result.username,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Not logged in — that's fine
|
||||
}
|
||||
renderAuthUI();
|
||||
}
|
||||
|
||||
// ============ Login ============
|
||||
async function login(username, password) {
|
||||
const body = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'call',
|
||||
params: {
|
||||
db: 'game',
|
||||
login: username,
|
||||
password: password,
|
||||
},
|
||||
id: Date.now(),
|
||||
};
|
||||
|
||||
const resp = await fetch(AUTH_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
const data = await resp.json();
|
||||
if (data.error) {
|
||||
throw new Error(data.error.data?.message || '登录失败,请检查用户名和密码');
|
||||
}
|
||||
|
||||
if (data.result && data.result.uid) {
|
||||
currentUser = {
|
||||
uid: data.result.uid,
|
||||
username: data.result.username,
|
||||
name: data.result.name || data.result.username,
|
||||
};
|
||||
renderAuthUI();
|
||||
return currentUser;
|
||||
}
|
||||
throw new Error('登录失败');
|
||||
}
|
||||
|
||||
// ============ Logout ============
|
||||
async function logout() {
|
||||
try {
|
||||
await fetch(DESTROY_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ jsonrpc: '2.0', method: 'call', params: {}, id: Date.now() }),
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
currentUser = null;
|
||||
renderAuthUI();
|
||||
}
|
||||
|
||||
// ============ Render ============
|
||||
function renderAuthUI() {
|
||||
const container = document.getElementById('auth-area');
|
||||
if (!container) return;
|
||||
|
||||
if (currentUser) {
|
||||
container.innerHTML =
|
||||
'<span class="user-name">' +
|
||||
escapeHtml(currentUser.name) +
|
||||
'</span>' +
|
||||
'<button class="auth-btn logout-btn" onclick="YuSenAuth.logout()">退出</button>';
|
||||
} else {
|
||||
container.innerHTML =
|
||||
'<button class="auth-btn login-btn" onclick="YuSenAuth.showLoginModal()">登录</button>';
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Login Modal ============
|
||||
function showLoginModal() {
|
||||
// Remove existing modal
|
||||
const existing = document.getElementById('login-modal');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'login-modal';
|
||||
modal.innerHTML =
|
||||
'<div class="login-overlay" onclick="YuSenAuth.hideLoginModal(event)">' +
|
||||
'<div class="login-box" onclick="event.stopPropagation()">' +
|
||||
'<h2>登录宇森</h2>' +
|
||||
'<p class="login-sub">使用 Odoo 账号登录</p>' +
|
||||
'<form id="login-form" onsubmit="return false">' +
|
||||
'<input type="text" id="login-username" placeholder="用户名" autocomplete="username" />' +
|
||||
'<input type="password" id="login-password" placeholder="密码" autocomplete="current-password" />' +
|
||||
'<div id="login-error" class="login-error"></div>' +
|
||||
'<button type="submit" class="login-submit" id="login-submit-btn">登 录</button>' +
|
||||
'</form>' +
|
||||
'<p class="login-footer">还没有账号?前往 <a href="/admin/web/login" target="_blank">Odoo 注册</a></p>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const form = modal.querySelector('#login-form');
|
||||
form.onsubmit = handleLogin;
|
||||
|
||||
// Focus username
|
||||
setTimeout(() => {
|
||||
const u = document.getElementById('login-username');
|
||||
if (u) u.focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function hideLoginModal(e) {
|
||||
if (e && e.target !== e.currentTarget) return;
|
||||
const modal = document.getElementById('login-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
const username = document.getElementById('login-username').value.trim();
|
||||
const password = document.getElementById('login-password').value;
|
||||
const errEl = document.getElementById('login-error');
|
||||
const btn = document.getElementById('login-submit-btn');
|
||||
|
||||
if (!username || !password) {
|
||||
errEl.textContent = '请输入用户名和密码';
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.textContent = '登录中...';
|
||||
errEl.textContent = '';
|
||||
|
||||
try {
|
||||
await login(username, password);
|
||||
hideLoginModal({ target: document.querySelector('.login-overlay') });
|
||||
} catch (e) {
|
||||
errEl.textContent = e.message;
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '登 录';
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// ============ Public API ============
|
||||
window.YuSenAuth = {
|
||||
init,
|
||||
login,
|
||||
logout,
|
||||
showLoginModal,
|
||||
hideLoginModal,
|
||||
getCurrentUser: () => currentUser,
|
||||
isLoggedIn: () => !!currentUser,
|
||||
};
|
||||
})();
|
||||
@ -28,7 +28,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
#topBar .nav-links { display:flex; gap:16px; margin-left:24px; }
|
||||
#topBar .nav-links a { color:#667788; text-decoration:none; font-size:12px; transition:color 0.2s; }
|
||||
#topBar .nav-links a:hover { color:#4af; }
|
||||
#topBar .coords { margin-left:auto; font-size:12px; color:#8899aa; }
|
||||
#topBar .coords { margin-left:8px; font-size:12px; color:#8899aa; }
|
||||
|
||||
/* 微观世界视图 */
|
||||
#microView {
|
||||
@ -164,6 +164,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
cursor: pointer; font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="auth.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -185,6 +186,7 @@ body { background: #0a0e17; overflow: hidden; font-family: 'Microsoft YaHei', sa
|
||||
<a href="world.html">世界</a>
|
||||
<a href="races.html">种族</a>
|
||||
</div>
|
||||
<div id="auth-area" style="margin-left:auto; margin-right:8px;"></div>
|
||||
<span class="coords" id="coords">坐标: (0, 0)</span>
|
||||
</div>
|
||||
|
||||
@ -1078,5 +1080,7 @@ window.addEventListener('resize', () => {
|
||||
generateMap();
|
||||
init();
|
||||
</script>
|
||||
<script src="auth.js"></script>
|
||||
<script>YuSenAuth.init();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -83,6 +83,7 @@ nav .links a:hover { color:#4af; }
|
||||
.quick-card { padding:14px 18px; min-width:110px; }
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="auth.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -90,6 +91,7 @@ nav .links a:hover { color:#4af; }
|
||||
|
||||
<nav>
|
||||
<span class="logo">宇森</span>
|
||||
<div id="auth-area"></div>
|
||||
<ul class="links">
|
||||
<li><a href="intro.html">游戏介绍</a></li>
|
||||
<li><a href="platform.html">平台架构</a></li>
|
||||
@ -177,5 +179,7 @@ function animate() {
|
||||
}
|
||||
animate();
|
||||
</script>
|
||||
<script src="auth.js"></script>
|
||||
<script>YuSenAuth.init();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -62,6 +62,7 @@ p { color:#99aabb; font-size:15px; margin-bottom:16px; line-height:1.9; }
|
||||
.page { padding:80px 20px 40px; }
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="auth.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -69,6 +70,7 @@ p { color:#99aabb; font-size:15px; margin-bottom:16px; line-height:1.9; }
|
||||
|
||||
<nav>
|
||||
<a href="index.html" class="logo">宇森</a>
|
||||
<div id="auth-area"></div>
|
||||
<ul class="links">
|
||||
<li><a href="intro.html" class="active">游戏介绍</a></li>
|
||||
<li><a href="platform.html">平台架构</a></li>
|
||||
@ -146,5 +148,7 @@ for (let i=0;i<80;i++) particles.push(new Particle());
|
||||
function animate() { ctx.clearRect(0,0,canvas.width,canvas.height); particles.forEach(p=>{p.update();p.draw();}); requestAnimationFrame(animate); }
|
||||
animate();
|
||||
</script>
|
||||
<script src="auth.js"></script>
|
||||
<script>YuSenAuth.init();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -56,6 +56,7 @@ h1 { font-size:32px; font-weight:bold; margin-bottom:8px; background:linear-grad
|
||||
h1 { font-size:24px; } nav .links { display:none; } .page { padding:80px 20px 40px; }
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="auth.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -63,6 +64,7 @@ h1 { font-size:32px; font-weight:bold; margin-bottom:8px; background:linear-grad
|
||||
|
||||
<nav>
|
||||
<a href="index.html" class="logo">宇森</a>
|
||||
<div id="auth-area"></div>
|
||||
<ul class="links">
|
||||
<li><a href="intro.html">游戏介绍</a></li>
|
||||
<li><a href="platform.html" class="active">平台架构</a></li>
|
||||
@ -114,5 +116,7 @@ class Particle{constructor(){this.r();this.y=Math.random()*c.height}r(){this.x=M
|
||||
for(let i=0;i<80;i++)P.push(new Particle());
|
||||
(function A(){x.clearRect(0,0,c.width,c.height);P.forEach(p=>{p.u();p.d()});requestAnimationFrame(A)})();
|
||||
</script>
|
||||
<script src="auth.js"></script>
|
||||
<script>YuSenAuth.init();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -49,6 +49,7 @@ h1 { font-size:32px; font-weight:bold; margin-bottom:8px; background:linear-grad
|
||||
.races { grid-template-columns:1fr; } .race-card { padding:22px 18px; }
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="auth.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -56,6 +57,7 @@ h1 { font-size:32px; font-weight:bold; margin-bottom:8px; background:linear-grad
|
||||
|
||||
<nav>
|
||||
<a href="index.html" class="logo">宇森</a>
|
||||
<div id="auth-area"></div>
|
||||
<ul class="links">
|
||||
<li><a href="intro.html">游戏介绍</a></li>
|
||||
<li><a href="platform.html">平台架构</a></li>
|
||||
@ -126,5 +128,7 @@ class Particle{constructor(){this.r();this.y=Math.random()*c.height}r(){this.x=M
|
||||
for(let i=0;i<80;i++)P.push(new Particle());
|
||||
(function A(){x.clearRect(0,0,c.width,c.height);P.forEach(p=>{p.u();p.d()});requestAnimationFrame(A)})();
|
||||
</script>
|
||||
<script src="auth.js"></script>
|
||||
<script>YuSenAuth.init();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -51,6 +51,7 @@ h1 { font-size:32px; font-weight:bold; margin-bottom:8px; background:linear-grad
|
||||
.layer-icon { padding-top:0; }
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="auth.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@ -58,6 +59,7 @@ h1 { font-size:32px; font-weight:bold; margin-bottom:8px; background:linear-grad
|
||||
|
||||
<nav>
|
||||
<a href="index.html" class="logo">宇森</a>
|
||||
<div id="auth-area"></div>
|
||||
<ul class="links">
|
||||
<li><a href="intro.html">游戏介绍</a></li>
|
||||
<li><a href="platform.html">平台架构</a></li>
|
||||
@ -134,5 +136,7 @@ class Particle{constructor(){this.r();this.y=Math.random()*c.height}r(){this.x=M
|
||||
for(let i=0;i<80;i++)P.push(new Particle());
|
||||
(function A(){x.clearRect(0,0,c.width,c.height);P.forEach(p=>{p.u();p.d()});requestAnimationFrame(A)})();
|
||||
</script>
|
||||
<script src="auth.js"></script>
|
||||
<script>YuSenAuth.init();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user