Files
yunzerwebsiteallinone/go/views/auth/login.tpl
T
2026-09-25 00:27:15 +08:00

799 lines
29 KiB
Smarty
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">
<meta name="robots" content="noindex, nofollow">
<title>统一认证中心 - 登录</title>
<style>
:root {
--primary: #3973ff;
--primary-dark: #2560a9;
--primary-light: #52a8ff;
--text: #1f2b48;
--text-sub: #5b6780;
--muted: #8a94ad;
--border: #e3e9f5;
--radius: 12px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
color: var(--text);
background: linear-gradient(120deg, #eef4ff 0%, #f7fbff 55%, #eaf3ff 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 20px;
position: relative;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
.glow {
position: fixed;
border-radius: 50%;
filter: blur(60px);
pointer-events: none;
z-index: 0;
}
.glow-1 { width: 380px; height: 380px; top: -120px; left: -100px; background: rgba(82, 168, 255, 0.28); }
.glow-2 { width: 320px; height: 320px; bottom: -120px; right: -80px; background: rgba(57, 115, 255, 0.20); }
.auth-card {
position: relative;
z-index: 1;
display: flex;
width: 100%;
max-width: 940px;
min-height: 560px;
background: #fff;
border-radius: 22px;
box-shadow: 0 18px 50px 0 rgba(37, 96, 169, 0.14), 0 2px 6px 0 rgba(31, 43, 72, 0.04);
overflow: hidden;
}
/* ---------- 左侧品牌区 ---------- */
.auth-side {
width: 340px;
flex: 0 0 340px;
padding: 44px 34px 28px;
background: linear-gradient(160deg, #1b3b8f 0%, #2f6fe4 52%, #52a8ff 100%);
color: #fff;
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
}
.auth-side::before {
content: '';
position: absolute;
width: 260px; height: 260px;
top: -90px; right: -110px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.10);
}
.auth-side::after {
content: '';
position: absolute;
width: 190px; height: 190px;
bottom: -70px; left: -60px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
}
.brand { display: flex; align-items: center; gap: 12px; position: relative; z-index: 1; }
.brand-logo {
width: 40px; height: 40px;
border-radius: 11px;
background: rgba(255, 255, 255, 0.18);
border: 1px solid rgba(255, 255, 255, 0.30);
display: flex; align-items: center; justify-content: center;
flex: 0 0 auto;
}
.brand-logo svg { width: 22px; height: 22px; }
.brand-name { font-size: 16px; font-weight: 600; letter-spacing: .5px; }
.side-title {
margin-top: 44px;
font-size: 26px;
font-weight: 600;
line-height: 1.45;
position: relative; z-index: 1;
}
.side-desc {
margin-top: 12px;
font-size: 13.5px;
line-height: 1.9;
color: rgba(255, 255, 255, 0.76);
position: relative; z-index: 1;
}
.side-illus { margin: auto 0; padding: 22px 0; position: relative; z-index: 1; }
.side-illus svg { width: 100%; height: auto; display: block; }
.side-foot {
position: relative; z-index: 1;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.16);
font-size: 12px;
color: rgba(255, 255, 255, 0.62);
display: flex;
align-items: center;
gap: 6px;
}
.side-foot svg { width: 13px; height: 13px; }
/* ---------- 右侧表单区 ---------- */
.auth-main {
flex: 1;
min-width: 0;
padding: 52px 56px 40px;
display: flex;
flex-direction: column;
justify-content: center;
}
.panel-head { margin-bottom: 28px; }
.panel-title { font-size: 27px; font-weight: 600; letter-spacing: .5px; }
.panel-sub { margin-top: 9px; font-size: 13.5px; color: var(--text-sub); }
.error {
display: none;
align-items: flex-start;
gap: 9px;
background: #fff2f0;
border: 1px solid #ffd4cf;
color: #cf1322;
padding: 11px 14px;
border-radius: 10px;
font-size: 13px;
line-height: 1.6;
margin-bottom: 18px;
animation: shake .32s ease;
}
.error.show { display: flex; }
.error svg { width: 16px; height: 16px; flex: 0 0 auto; margin-top: 2px; }
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-4px); }
75% { transform: translateX(4px); }
}
.field { margin-bottom: 18px; }
.field-label {
display: flex; align-items: center; justify-content: space-between;
font-size: 13px; color: var(--text-sub); margin-bottom: 8px; font-weight: 500;
}
.input-wrap { position: relative; }
.input-icon {
position: absolute;
left: 14px; top: 50%;
transform: translateY(-50%);
width: 18px; height: 18px;
color: #a7b1c7;
pointer-events: none;
transition: color .2s;
}
.input-wrap input {
width: 100%;
height: 46px;
padding: 0 44px 0 42px;
font-size: 14px;
color: var(--text);
border: 1px solid var(--border);
border-radius: var(--radius);
background: #fbfcff;
outline: none;
transition: border-color .2s, box-shadow .2s, background .2s;
}
.input-wrap input::placeholder { color: #adb6c9; }
.input-wrap input:hover { border-color: var(--border); background: #fff; }
.input-wrap input:focus {
border-color: var(--primary);
background: #fff;
box-shadow: 0 0 0 4px rgba(57, 115, 255, 0.12);
}
.input-wrap input:focus + .input-icon,
.input-wrap:focus-within .input-icon { color: var(--primary); }
.eye-btn {
position: absolute;
right: 8px; top: 50%;
transform: translateY(-50%);
width: 32px; height: 32px;
border: none; background: transparent;
border-radius: 8px;
color: #a7b1c7;
cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: color .2s, background .2s;
}
.eye-btn:hover { color: var(--primary); background: #f0f5ff; }
.eye-btn svg { width: 18px; height: 18px; }
.row-extra {
display: flex; align-items: center; justify-content: space-between;
margin: -4px 0 22px;
font-size: 13px;
}
.remember { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; color: var(--text-sub); user-select: none; }
.remember input { width: 15px; height: 15px; accent-color: var(--primary); cursor: pointer; }
.link { color: var(--primary); text-decoration: none; cursor: pointer; transition: color .2s; }
.link:hover { color: var(--primary-dark); text-decoration: underline; }
.btn {
width: 100%;
height: 46px;
border: none;
border-radius: var(--radius);
cursor: pointer;
background: linear-gradient(135deg, #3973ff 0%, #52a8ff 100%);
color: #fff;
font-size: 15.5px;
font-weight: 500;
letter-spacing: 2px;
display: flex; align-items: center; justify-content: center; gap: 8px;
box-shadow: 0 8px 20px rgba(57, 115, 255, 0.28);
transition: transform .15s, box-shadow .2s, opacity .2s;
}
.btn:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 12px 26px rgba(57, 115, 255, 0.34); }
.btn:active:not(:disabled) { transform: translateY(0); box-shadow: 0 6px 14px rgba(57, 115, 255, 0.26); }
.btn:disabled { opacity: .72; cursor: not-allowed; box-shadow: none; }
.spinner {
display: none;
width: 16px; height: 16px;
border: 2px solid rgba(255, 255, 255, 0.45);
border-top-color: #fff;
border-radius: 50%;
animation: spin .7s linear infinite;
}
.btn.is-loading .spinner { display: block; }
@keyframes spin { to { transform: rotate(360deg); } }
/* 第三方登录 */
.third-area { margin-top: 4px; }
.divider {
position: relative; text-align: center;
margin: 26px 0 16px;
font-size: 12px; color: #a3acbf;
}
.divider::before {
content: ''; position: absolute; top: 50%; left: 0; right: 0;
height: 1px; background: var(--border);
}
.divider span { position: relative; background: #fff; padding: 0 12px; }
.third-list { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.third-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 9px 18px;
border: 1px solid var(--border);
border-radius: 22px;
background: #fff;
font-size: 13px;
color: var(--text-sub);
cursor: pointer;
transition: all .2s;
}
.third-btn:hover {
border-color: var(--primary);
color: var(--primary);
background: #f5f9ff;
transform: translateY(-1px);
}
/* 企业选择 */
.tenant-list { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.tenant-item {
display: flex; align-items: center; gap: 14px;
padding: 14px 16px;
border: 1px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
background: #fbfcff;
transition: all .2s;
}
.tenant-item:hover {
border-color: var(--primary);
background: #f5f9ff;
transform: translateY(-1px);
box-shadow: 0 8px 18px rgba(57, 115, 255, 0.10);
}
.tenant-avatar {
width: 40px; height: 40px;
flex: 0 0 40px;
border-radius: 10px;
background: linear-gradient(135deg, rgba(57, 115, 255, 0.12) 0%, rgba(82, 168, 255, 0.16) 100%);
color: var(--primary);
display: flex; align-items: center; justify-content: center;
}
.tenant-avatar svg { width: 20px; height: 20px; }
.tenant-info { flex: 1; min-width: 0; }
.tenant-name {
font-size: 15px; font-weight: 500; color: var(--text);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tenant-meta { font-size: 12px; color: var(--muted); margin-top: 4px; }
.tenant-arrow { color: #c8cfdf; transition: transform .2s, color .2s; }
.tenant-arrow svg { width: 18px; height: 18px; display: block; }
.tenant-item:hover .tenant-arrow { color: var(--primary); transform: translateX(3px); }
.back { text-align: center; margin-top: 22px; font-size: 13px; }
.foot-tip {
margin-top: 26px;
padding-top: 18px;
border-top: 1px dashed var(--border);
font-size: 12px;
color: var(--muted);
display: flex; align-items: center; gap: 6px;
}
.foot-tip svg { width: 13px; height: 13px; flex: 0 0 auto; }
@media (max-width: 900px) {
body { padding: 20px 14px; align-items: flex-start; }
.auth-card { max-width: 520px; min-height: 0; flex-direction: column; }
.auth-side { width: 100%; flex: none; padding: 30px 26px 24px; }
.side-title { margin-top: 26px; font-size: 21px; }
.side-illus { display: none; }
.auth-main { padding: 34px 26px 30px; }
.panel-title { font-size: 23px; }
}
</style>
</head>
<body>
<div class="glow glow-1"></div>
<div class="glow glow-2"></div>
<div class="auth-card">
<!-- 左侧品牌区 -->
<aside class="auth-side">
<div class="brand">
<span class="brand-logo">
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3l7 3v6c0 4.2-2.9 7.6-7 9-4.1-1.4-7-4.8-7-9V6l7-3z"/>
<path d="M9.2 12.2l2 2 3.6-4"/>
</svg>
</span>
<span class="brand-name">云泽统一认证中心</span>
</div>
<div class="side-title">一次登录<br>全平台通行</div>
<div class="side-desc">
基于 OIDC / OAuth 2.0 的统一身份底座,<br>
为所有接入应用提供安全、一致的登录体验。
</div>
<div class="side-illus">
<svg viewBox="0 0 260 170" fill="none">
<ellipse cx="130" cy="150" rx="98" ry="11" fill="rgba(255,255,255,.13)"/>
<rect x="26" y="60" width="56" height="38" rx="9" fill="rgba(255,255,255,.14)"/>
<rect x="36" y="70" width="26" height="4" rx="2" fill="rgba(255,255,255,.45)"/>
<rect x="36" y="79" width="36" height="4" rx="2" fill="rgba(255,255,255,.28)"/>
<rect x="184" y="52" width="50" height="34" rx="9" fill="rgba(255,255,255,.10)"/>
<rect x="193" y="62" width="22" height="4" rx="2" fill="rgba(255,255,255,.36)"/>
<path d="M130 26l40 15v30c0 22-17 39-40 47-23-8-40-25-40-47V41l40-15z"
fill="rgba(255,255,255,.17)" stroke="rgba(255,255,255,.55)" stroke-width="1.6"/>
<path d="M116 82l11 11 21-23" stroke="#fff" stroke-width="5.5"
stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="side-foot">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<rect x="4" y="11" width="16" height="10" rx="2"/>
<path d="M8 11V8a4 4 0 018 0v3"/>
</svg>
<span>© 2026 Yunzer · 连接已加密</span>
</div>
</aside>
<!-- 右侧表单区 -->
<main class="auth-main">
{{if eq .Step "tenant"}}
<div class="panel-head">
<h1 class="panel-title">选择企业</h1>
<p class="panel-sub">该账号已加入多家企业,请选择要进入的空间</p>
</div>
<div class="error" id="error">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/>
</svg>
<span id="errorText"></span>
</div>
<ul class="tenant-list">
{{range .Tenants}}
<li class="tenant-item" onclick="chooseTenant({{.Tid}})">
<span class="tenant-avatar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 21h18M5 21V7l7-4 7 4v14"/><path d="M10 21v-5h4v5"/>
</svg>
</span>
<span class="tenant-info">
<span class="tenant-name">{{.TenantName}}</span>
<span class="tenant-meta">{{if .Name}}{{.Name}} · {{end}}企业ID {{.Tid}}</span>
</span>
<span class="tenant-arrow">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 6l6 6-6 6"/>
</svg>
</span>
</li>
{{end}}
</ul>
<div class="back"><a class="link" onclick="logout()">不是我的账号?切换账号</a></div>
{{else}}
<div class="panel-head">
<h1 class="panel-title">欢迎登录</h1>
<p class="panel-sub">请输入您的账号与密码以继续</p>
</div>
<div class="error" id="error">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
<circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/>
</svg>
<span id="errorText"></span>
</div>
<form id="loginForm" onsubmit="return submitLogin(event)" autocomplete="on">
<div class="field">
<label class="field-label" for="account">账号</label>
<div class="input-wrap">
<input id="account" type="text" placeholder="手机号 / 邮箱 / 企业账号" autocomplete="username" autofocus>
<svg class="input-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 3.6-6 8-6s8 2 8 6"/>
</svg>
</div>
</div>
<div class="field">
<label class="field-label" for="password">密码</label>
<div class="input-wrap">
<input id="password" type="password" placeholder="请输入密码" autocomplete="current-password">
<svg class="input-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V8a4 4 0 018 0v3"/>
</svg>
<button class="eye-btn" type="button" id="eyeBtn" onclick="togglePassword()" title="显示密码" aria-label="显示密码">
<svg id="eyeOpen" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 12s3.6-6 10-6 10 6 10 6-3.6 6-10 6-10-6-10-6z"/><circle cx="12" cy="12" r="3"/>
</svg>
<svg id="eyeOff" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" style="display:none">
<path d="M3 3l18 18"/><path d="M10.6 6.2A9.9 9.9 0 0112 6c6.4 0 10 6 10 6a17 17 0 01-3.3 3.9M6.3 8.1A16.7 16.7 0 002 12s3.6 6 10 6c1.3 0 2.5-.3 3.6-.7"/>
</svg>
</button>
</div>
</div>
<div class="row-extra">
<label class="remember"><input type="checkbox" id="remember"> 记住账号</label>
</div>
<button class="btn" id="submitBtn" type="submit">
<span class="spinner"></span>
<span id="btnLabel">登 录</span>
</button>
</form>
<div class="third-area" id="thirdArea" style="display:none">
<div class="divider"><span>其他登录方式</span></div>
<div class="third-list" id="thirdList"></div>
</div>
{{end}}
<div class="foot-tip">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3l7 3v6c0 4.2-2.9 7.6-7 9-4.1-1.4-7-4.8-7-9V6l7-3z"/>
</svg>
<span>统一身份认证 · 一次登录,全平台通行</span>
</div>
</main>
</div>
<script>
var CLIENT_ID = "{{.ClientID}}";
var REDIRECT = "{{.Redirect}}";
// 人机验证配置:页面加载时拉取,决定是否弹出极验
var verifyCfg = { openVerify: 0, verifyType: '', needGeetest: false, geetestVersion: '4', captchaId: '' };
// 极验 4.0 SDK:优先官方 CDN,失败回退到本地副本(static/js/gt4.js)
var GEETEST_SDK_CDN = 'https://static.geetest.com/v4/gt4.js';
var GEETEST_SDK_LOCAL = '/static/js/gt4.js';
var geetestSDKPromise = null;
function showError(msg) {
var box = document.getElementById('error');
document.getElementById('errorText').textContent = msg;
box.classList.remove('show');
void box.offsetWidth; // 重启动画
box.classList.add('show');
}
function hideError() {
var box = document.getElementById('error');
if (box) box.classList.remove('show');
}
function setBtnLoading(on, text) {
var btn = document.getElementById('submitBtn');
if (!btn) return;
btn.disabled = !!on;
if (on) {
btn.classList.add('is-loading');
document.getElementById('btnLabel').textContent = text || '登录中...';
} else {
btn.classList.remove('is-loading');
document.getElementById('btnLabel').textContent = '登 录';
}
}
function togglePassword() {
var input = document.getElementById('password');
var open = document.getElementById('eyeOpen');
var off = document.getElementById('eyeOff');
var btn = document.getElementById('eyeBtn');
var show = input.type === 'password';
input.type = show ? 'text' : 'password';
open.style.display = show ? 'none' : 'block';
off.style.display = show ? 'block' : 'none';
btn.title = show ? '隐藏密码' : '显示密码';
}
// 记住账号:只存账号,绝不明文保存密码
(function initRemember() {
var box = document.getElementById('remember');
if (!box) return;
var saved = '';
try { saved = localStorage.getItem('authCenterAccount') || ''; } catch (e) { saved = ''; }
if (saved) {
document.getElementById('account').value = saved;
box.checked = true;
}
box.addEventListener('change', function () {
try {
if (box.checked) localStorage.setItem('authCenterAccount', document.getElementById('account').value.trim());
else localStorage.removeItem('authCenterAccount');
} catch (e) { /* 忽略隐私模式下的存储失败 */ }
});
})();
function loadScript(url) {
return new Promise(function (resolve, reject) {
var s = document.createElement('script');
s.src = url;
s.async = true;
s.onload = function () { resolve(); };
s.onerror = function () { reject(new Error('加载脚本失败')); };
document.head.appendChild(s);
});
}
// 加载极验 4.0 SDK(多次调用只加载一次)
function loadGeetestSDK() {
if (window.initGeetest4) return Promise.resolve();
if (geetestSDKPromise) return geetestSDKPromise;
geetestSDKPromise = loadScript(GEETEST_SDK_CDN)
.catch(function () { return loadScript(GEETEST_SDK_LOCAL); })
.then(function () {
if (!window.initGeetest4) throw new Error('极验 SDK 加载失败');
})
.catch(function (err) {
geetestSDKPromise = null;
throw err;
});
return geetestSDKPromise;
}
// 拉取登录验证配置(含极验 captcha_id)
function loadVerifyConfig() {
return fetch('/auth/verify-config')
.then(function (r) { return r.json(); })
.then(function (res) {
var d = (res && res.data) || {};
verifyCfg.openVerify = Number(d.openVerify) || 0;
verifyCfg.verifyType = d.verifyType || '';
verifyCfg.needGeetest = !!d.needGeetest;
verifyCfg.geetestVersion = d.geetestVersion || '4';
verifyCfg.captchaId = d.captchaId || '';
// 预加载 SDK,点击登录时能立刻弹出
if (verifyCfg.needGeetest && verifyCfg.captchaId) {
loadGeetestSDK().catch(function () { /* 点击时再提示 */ });
}
})
.catch(function () { verifyCfg.needGeetest = false; });
}
loadVerifyConfig();
// 加载已开通的第三方登录方式
function loadThirdProviders() {
fetch('/auth/third/list')
.then(function (r) { return r.json(); })
.then(function (res) {
var list = (res && res.data) || [];
var enabled = list.filter(function (i) { return i.enabled; });
if (!enabled.length) return;
var box = document.getElementById('thirdList');
enabled.forEach(function (item) {
var btn = document.createElement('button');
btn.className = 'third-btn';
btn.type = 'button';
btn.textContent = item.name;
btn.onclick = function () { startThird(item.provider); };
box.appendChild(btn);
});
document.getElementById('thirdArea').style.display = 'block';
})
.catch(function () { /* 静默失败,不影响账号密码登录 */ });
}
function startThird(provider) {
window.location.href = '/auth/third/' + encodeURIComponent(provider) +
'/authorize?redirect=' + encodeURIComponent(REDIRECT);
}
loadThirdProviders();
// 弹出极验 4.0,验证通过才 resolve 出校验参数
function showGeetest(captchaId) {
return loadGeetestSDK().then(function () {
return new Promise(function (resolve, reject) {
var settled = false;
var done = function (fn) { if (settled) return; settled = true; fn(); };
var instance = null;
var teardown = function () {
try { if (instance && instance.destroy) instance.destroy(); } catch (err) { /* ignore */ }
};
window.initGeetest4({
captchaId: captchaId,
product: 'bind',
language: 'zh-CN',
https: true
}, function (captcha) {
instance = captcha;
captcha.onSuccess(function () {
var r = captcha.getValidate() || {};
teardown();
done(function () {
resolve({
captcha_id: r.captcha_id || captchaId,
lot_number: r.lot_number || '',
pass_token: r.pass_token || '',
gen_time: r.gen_time || '',
captcha_output: r.captcha_output || ''
});
});
});
captcha.onFail(function () {
teardown();
done(function () { reject(new Error('人机验证未通过,请重新验证')); });
});
captcha.onError(function () {
teardown();
done(function () { reject(new Error('人机验证加载失败,请重试')); });
});
if (captcha.onClose) {
captcha.onClose(function () {
setTimeout(function () {
if (settled) return;
teardown();
done(function () { reject(new Error('已取消人机验证')); });
}, 400);
});
}
captcha.showCaptcha();
});
});
});
}
function submitLogin(e) {
e.preventDefault();
hideError();
var account = document.getElementById('account').value.trim();
var password = document.getElementById('password').value;
if (!account) { showError('请输入账号'); return false; }
if (!password) { showError('请输入密码'); return false; }
// 记住账号
try {
if (document.getElementById('remember').checked) localStorage.setItem('authCenterAccount', account);
else localStorage.removeItem('authCenterAccount');
} catch (err) { /* ignore */ }
// 开启极验时:先弹验证,通过后才继续登录
if (verifyCfg.needGeetest) {
if (!verifyCfg.captchaId) {
showError('人机验证未配置,请联系管理员');
return false;
}
setBtnLoading(true, '安全验证中...');
showGeetest(verifyCfg.captchaId)
.then(function (result) {
setBtnLoading(true, '登录中...');
doLogin(account, password, result);
})
.catch(function (err) {
showError(err.message || '人机验证失败,请重试');
setBtnLoading(false);
});
return false;
}
setBtnLoading(true, '登录中...');
doLogin(account, password, null);
return false;
}
function doLogin(account, password, captcha) {
var payload = { account: account, password: password, client_id: CLIENT_ID, redirect: REDIRECT };
if (captcha) {
payload.captcha_id = captcha.captcha_id;
payload.lot_number = captcha.lot_number;
payload.pass_token = captcha.pass_token;
payload.gen_time = captcha.gen_time;
payload.captcha_output = captcha.captcha_output;
}
fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(function (r) { return r.json(); })
.then(function (res) {
if (res.code !== 200) {
showError(res.msg || '登录失败');
setBtnLoading(false);
return;
}
if (res.need_choose_tenant) {
window.location.href = '/auth/login?step=tenant&client_id=' + encodeURIComponent(CLIENT_ID) + '&redirect=' + encodeURIComponent(REDIRECT);
return;
}
if (res.redirect) { window.location.href = res.redirect; return; }
window.location.href = '/auth/login';
})
.catch(function () {
showError('网络异常,请稍后重试');
setBtnLoading(false);
});
}
function chooseTenant(tid) {
hideError();
fetch('/auth/switch-tenant', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tid: tid, client_id: CLIENT_ID, redirect: REDIRECT })
})
.then(function (r) { return r.json(); })
.then(function (res) {
if (res.code !== 200) { showError(res.msg || '切换失败'); return; }
if (res.redirect) { window.location.href = res.redirect; return; }
window.location.href = '/auth/login';
})
.catch(function () { showError('网络异常,请稍后重试'); });
}
function logout() {
fetch('/auth/logout', { method: 'POST' }).then(function () {
window.location.href = '/auth/login?client_id=' + encodeURIComponent(CLIENT_ID) + '&redirect=' + encodeURIComponent(REDIRECT);
});
}
</script>
</body>
</html>