Files
yunzerwebsiteallinone/go/views/auth/login.tpl
T
2026-11-11 16:58:30 +08:00

1339 lines
52 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>{{.LoginTitle}} - 统一认证中心</title>
<style>
:root {
--primary: #3973ff;
--primary-dark: #2560a9;
--primary-light: #52a8ff;
--text: #1f2b48;
--text-sub: #5b6780;
--muted: #8a94ad;
--border: #e3e9f5;
--radius: 10px;
}
* { 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: #f3f6fc;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
.login-page { display: flex; min-height: 100vh; width: 100%; }
/* ---------- 左侧品牌区 ---------- */
.brand-panel {
position: relative;
flex: 1 1 58%;
min-height: 100vh;
padding: 64px 72px 52px;
display: flex;
flex-direction: column;
justify-content: center;
overflow: hidden;
background: linear-gradient(150deg, #1b3b8f 0%, #2f6fe4 52%, #52a8ff 100%);
color: #fff;
}
.brand-panel::before {
content: '';
position: absolute;
width: 420px; height: 420px;
top: -150px; right: -160px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.10);
}
.brand-panel::after {
content: '';
position: absolute;
width: 300px; height: 300px;
bottom: -120px; left: -100px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
}
/* 配置了背景图:浅色底 + 深色文字,叠一层白色蒙版保证可读性 */
.brand-panel.has-bg { background: #eaf1fb; color: var(--text); }
.brand-panel.has-bg::before,
.brand-panel.has-bg::after { display: none; }
.brand-bg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.brand-veil {
display: block;
position: absolute;
inset: 0;
background: linear-gradient(100deg, rgba(255, 255, 255, 0.86) 0%, rgba(255, 255, 255, 0.46) 52%, rgba(255, 255, 255, 0.10) 100%);
}
.brand-inner {
position: relative;
z-index: 1;
width: 100%;
max-width: 580px;
}
.brand { display: flex; align-items: center; gap: 12px; margin-bottom: 54px; }
.brand-logo {
width: 44px; height: 44px;
border-radius: 12px;
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: 24px; height: 24px; }
.brand-name { font-size: 19px; font-weight: 600; letter-spacing: .5px; }
.brand-img { height: 44px; max-width: 240px; object-fit: contain; display: block; }
.brand-title {
font-size: clamp(34px, 3.6vw, 52px);
font-weight: 700;
line-height: 1.28;
letter-spacing: 1px;
white-space: pre-line;
}
.brand-sub {
margin-top: 20px;
font-size: 16px;
line-height: 1.9;
color: rgba(255, 255, 255, 0.78);
max-width: 520px;
white-space: pre-line;
}
.brand-panel.has-bg .brand-sub { color: var(--text-sub); }
.brand-panel.has-bg .brand-name { color: var(--text); }
.brand-panel.has-bg .brand-logo {
background: rgba(57, 115, 255, 0.12);
border-color: rgba(57, 115, 255, 0.18);
}
.brand-panel.has-bg .brand-logo svg { stroke: var(--primary); }
.brand-foot {
position: relative;
z-index: 1;
margin-top: 72px;
font-size: 12px;
color: rgba(255, 255, 255, 0.62);
display: flex;
align-items: center;
gap: 6px;
}
.brand-panel.has-bg .brand-foot { color: var(--muted); }
.brand-foot svg { width: 13px; height: 13px; flex: 0 0 auto; }
/* ---------- 右侧表单区 ---------- */
.form-panel {
position: relative;
flex: 0 0 42%;
min-width: 460px;
min-height: 100vh;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
padding: 64px 56px 88px;
}
.form-inner { width: 100%; max-width: 380px; }
.panel-head { margin-bottom: 30px; }
.panel-title { font-size: 30px; font-weight: 700; letter-spacing: .5px; }
.panel-sub { margin-top: 10px; font-size: 13.5px; color: var(--text-sub); line-height: 1.7; }
/* ---------- 登录方式 tab ---------- */
.login-tabs {
display: flex;
gap: 4px;
margin: 0 0 24px;
border-bottom: 1px solid var(--border);
}
.login-tab {
position: relative;
flex: 1 1 0;
padding: 11px 4px 13px;
border: none;
background: transparent;
font-size: 14px;
font-weight: 500;
color: var(--text-sub);
cursor: pointer;
transition: color .2s;
}
.login-tab:hover { color: var(--primary); }
.login-tab.active { color: var(--primary); font-weight: 600; }
.login-tab.active::after {
content: '';
position: absolute;
left: 12px; right: 12px; bottom: -1px;
height: 2px;
border-radius: 2px;
background: linear-gradient(90deg, var(--primary) 0%, var(--primary-light) 100%);
}
.login-panel { display: none; }
/* ---------- 登录方式全部关闭 ---------- */
.login-disabled {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 48px 24px 32px;
color: var(--text-sub);
}
.login-disabled svg {
width: 44px; height: 44px;
color: var(--text-faint, #a3acbf);
margin-bottom: 14px;
}
.login-disabled-title {
margin: 0 0 8px;
font-size: 16px;
font-weight: 600;
color: var(--text);
}
.login-disabled-desc {
margin: 0;
font-size: 13px;
line-height: 1.7;
}
.login-panel.active { display: block; animation: panelIn .24s ease both; }
@keyframes panelIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: none; }
}
/* 小屏时左侧品牌区隐藏,在表单上方展示紧凑品牌头 */
.mobile-brand { display: none; align-items: center; gap: 12px; margin-bottom: 28px; }
.mobile-brand .brand-img { height: 36px; max-width: 180px; }
.mobile-brand .brand-logo {
width: 40px; height: 40px;
background: rgba(57, 115, 255, 0.10);
border-color: rgba(57, 115, 255, 0.18);
}
.mobile-brand .brand-logo svg { stroke: var(--primary); }
.mobile-brand-text { display: flex; flex-direction: column; gap: 2px; }
.mobile-brand-name { font-size: 15px; font-weight: 600; }
.mobile-brand-sub { font-size: 12px; color: var(--muted); }
.form-foot {
position: absolute;
left: 0; right: 0; bottom: 26px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
text-align: center;
font-size: 12px;
color: #a3acbf;
}
/* 底部「清除缓存」文字按钮 */
.cache-clear-btn {
border: 0;
background: none;
padding: 0;
font: inherit;
color: var(--primary);
cursor: pointer;
transition: color .2s;
}
.cache-clear-btn:hover { color: var(--primary-dark); text-decoration: underline; }
.cache-clear-btn:disabled { opacity: .6; cursor: default; text-decoration: none; }
.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.is-info { background: #f2f7ff; border-color: #d2e2ff; color: #2b5fc7; }
.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); }
}
.notice-box {
display: flex; align-items: flex-start; gap: 9px;
background: #f0fff4; border: 1px solid #b7ebc2; color: #1f7a37;
padding: 11px 14px; border-radius: 10px; font-size: 13px; line-height: 1.6;
margin-bottom: 18px;
}
.notice-box.warn {
background: #fffbe6; border-color: #ffe89e; color: #a96a00;
}
.notice-box svg { width: 16px; height: 16px; flex: 0 0 auto; margin-top: 2px; }
.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: 8px;
padding: 8px 16px;
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);
}
/* 第三方图标色块:微信绿 / QQ 蓝 / 其余统一主色 */
.third-icon {
width: 22px; height: 22px;
flex: 0 0 22px;
border-radius: 50%;
display: inline-flex; align-items: center; justify-content: center;
font-size: 9.5px; font-weight: 700; letter-spacing: -.2px;
}
.third-icon svg { width: 13px; height: 13px; }
.third-icon.is-wechat { background: rgba(7, 193, 96, .12); color: #07c160; }
.third-icon.is-qq { background: rgba(18, 183, 245, .14); color: #12b7f5; }
.third-icon.is-generic { background: rgba(57, 115, 255, .10); color: var(--primary); }
/* 短信验证码 */
.code-row { display: flex; align-items: center; gap: 10px; }
.code-row .input-wrap { flex: 1 1 auto; }
.code-btn {
flex: 0 0 auto;
height: 46px;
padding: 0 16px;
border: 1px solid rgba(57, 115, 255, .28);
border-radius: var(--radius);
background: rgba(57, 115, 255, .06);
color: var(--primary);
font-size: 13px;
font-weight: 500;
white-space: nowrap;
cursor: pointer;
transition: background .2s, border-color .2s, color .2s;
}
.code-btn:hover { background: rgba(57, 115, 255, .12); border-color: rgba(57, 115, 255, .45); }
.code-btn:disabled { background: #f5f7fb; border-color: var(--border); color: var(--muted); cursor: not-allowed; }
.form-hint { margin-top: 14px; font-size: 12px; color: var(--muted); line-height: 1.8; text-align: center; }
/* 企业选择 */
.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: 18px; font-size: 13px; color: var(--text-sub); line-height: 1.7; }
/* ---------- 扫码登录 ---------- */
.qr-panel { padding: 4px 0 2px; text-align: center; }
.qr-box {
position: relative;
width: 200px; height: 200px;
margin: 0 auto 20px;
border: 1px solid var(--border);
border-radius: 16px;
background: #fff;
box-shadow: 0 14px 34px rgba(31, 43, 72, 0.08);
display: flex; align-items: center; justify-content: center;
overflow: hidden;
}
.qr-img { width: 100%; height: 100%; object-fit: contain; }
.qr-mask {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
padding: 12px;
background: rgba(255, 255, 255, .96);
font-size: 12.5px; color: var(--text-sub); line-height: 1.6; text-align: center;
white-space: pre-line;
}
.scan-btn {
border: none; background: var(--primary); color: #fff;
border-radius: 8px; padding: 7px 18px; font-size: 12px; cursor: pointer;
}
.qr-title { font-size: 14.5px; font-weight: 600; color: var(--text); letter-spacing: .3px; }
.qr-desc { margin-top: 8px; font-size: 12.5px; line-height: 1.9; color: var(--text-sub); }
.qr-desc strong { color: var(--primary); font-weight: 600; }
.qr-note { margin-top: 12px; font-size: 12px; color: var(--muted); }
.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: 1100px) {
.brand-panel { padding: 48px 44px; }
.brand-title { font-size: 34px; }
.form-panel { min-width: 400px; padding: 56px 40px 84px; }
}
@media (max-width: 900px) {
.login-page { flex-direction: column; }
.brand-panel { display: none; }
.form-panel {
flex: 1 1 auto;
min-width: 0;
min-height: 100vh;
align-items: flex-start;
padding: 40px 24px 72px;
}
.form-inner { max-width: 440px; margin: 0 auto; }
.mobile-brand { display: flex; }
.panel-title { font-size: 26px; }
}
</style>
</head>
<body>
<div class="login-page">
<!-- 左侧品牌区:背景图 / Logo / 主副标题由 platform「平台设置 → 登录页设置」提供 -->
<aside class="brand-panel{{if .LoginBg}} has-bg{{end}}">
{{if .LoginBg}}
<img class="brand-bg" src="{{.LoginBg}}" alt="" />
<span class="brand-veil"></span>
{{end}}
<div class="brand-inner">
<div class="brand">
{{if .LoginLogo}}
<img class="brand-img" src="{{.LoginLogo}}" alt="{{.LoginTitle}}" />
{{else}}
<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>
{{end}}
</div>
<h1 class="brand-title">{{.LoginTitle}}</h1>
<p class="brand-sub">{{.LoginSubtitle}}</p>
</div>
<div class="brand-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="form-panel">
<div class="form-inner">
<div class="mobile-brand">
{{if .LoginLogo}}
<img class="brand-img" src="{{.LoginLogo}}" alt="{{.LoginTitle}}" />
{{else}}
<span class="brand-logo">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" 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>
{{end}}
<span class="mobile-brand-text">
<span class="mobile-brand-name">温新云统一认证中心</span>
<span class="mobile-brand-sub">{{.LoginTitle}}</span>
</span>
</div>
{{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>
{{if .LoginDisabled}}
<!-- 平台设置已关闭全部登录方式 -->
<div class="login-disabled">
<svg 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>
<p class="login-disabled-title">登录功能已关闭</p>
<p class="login-disabled-desc">管理员已在平台设置中关闭全部登录方式,请联系管理员开启后再试。</p>
</div>
{{else}}
<!-- 登录方式切换:按平台设置的开关渲染,默认选中第一个可用方式 -->
{{if .LoginShowTabs}}
<div class="login-tabs" role="tablist">
{{if .LoginPwdOn}}<button class="login-tab{{if eq .LoginDefaultTab "account"}} active{{end}}" type="button" role="tab" data-tab="account" onclick="switchLoginTab('account')">账号密码</button>{{end}}
{{if .LoginSmsOn}}<button class="login-tab{{if eq .LoginDefaultTab "sms"}} active{{end}}" type="button" role="tab" data-tab="sms" onclick="switchLoginTab('sms')">短信登录</button>{{end}}
{{if .LoginQrOn}}<button class="login-tab{{if eq .LoginDefaultTab "qr"}} active{{end}}" type="button" role="tab" data-tab="qr" onclick="switchLoginTab('qr')">扫码登录</button>{{end}}
</div>
{{end}}
<!-- 账号密码登录 -->
{{if .LoginPwdOn}}
<div class="login-panel{{if eq .LoginDefaultTab "account"}} active{{end}}" id="panel-account">
{{if .Notice}}
<div class="notice-box{{if eq .Notice "registered_warn"}} warn{{end}}">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
{{if eq .Notice "registered_warn"}}<circle cx="12" cy="12" r="9"/><path d="M12 8v5M12 16h.01"/>{{else}}<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"/>{{end}}
</svg>
<span>
{{if eq .Notice "reset"}}密码已重置,请使用新密码登录{{else if eq .Notice "registered_warn"}}{{.WarnMsg}},可先登录{{else}}注册成功,请使用新账号登录{{end}}
</span>
</div>
{{end}}
<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>
<a class="link" href="/auth/forgot?client_id={{urlquery .ClientID}}&redirect={{urlquery .Redirect}}">忘记密码?</a>
</div>
<button class="btn" id="submitBtn" type="submit">
<span class="spinner"></span>
<span id="btnLabel">登 录</span>
</button>
</form>
<div class="back">还没有账号?<a class="link" href="/auth/register?client_id={{urlquery .ClientID}}&redirect={{urlquery .Redirect}}">立即注册</a></div>
</div>
{{end}}
<!-- 手机号验证码登录 -->
{{if .LoginSmsOn}}
<div class="login-panel{{if eq .LoginDefaultTab "sms"}} active{{end}}" id="panel-sms">
<form onsubmit="return submitSmsLogin(event)" autocomplete="on">
<div class="field">
<label class="field-label" for="smsMobile">手机号</label>
<div class="input-wrap">
<input id="smsMobile" type="tel" inputmode="numeric" maxlength="11" placeholder="请输入手机号" autocomplete="tel">
<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="6" y="2" width="12" height="20" rx="3"/><path d="M11 18h2"/>
</svg>
</div>
</div>
<div class="field">
<label class="field-label" for="smsCode">验证码</label>
<div class="code-row">
<div class="input-wrap">
<input id="smsCode" type="text" inputmode="numeric" maxlength="6" placeholder="请输入短信验证码" autocomplete="one-time-code">
<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="5" width="16" height="14" rx="2"/><path d="M8 9h8M8 13h5"/>
</svg>
</div>
<button class="code-btn" type="button" id="smsCodeBtn" onclick="sendSmsCode()">获取验证码</button>
</div>
</div>
<button class="btn" type="submit" id="smsSubmitBtn">
<span class="spinner"></span>
<span id="smsBtnLabel">登 录</span>
</button>
</form>
<p class="form-hint">未注册的手机号验证通过后将自动创建账号</p>
</div>
{{end}}
<!-- 扫码登录:微信扫码 → 小程序内确认 -->
{{if .LoginQrOn}}
<div class="login-panel{{if eq .LoginDefaultTab "qr"}} active{{end}}" id="panel-qr">
<div class="qr-panel">
<div class="qr-box">
<img id="scanQrImg" class="qr-img" alt="微信扫码登录" style="display:none" />
<div class="qr-mask" id="scanQrMask">
<span id="scanQrMaskText">正在获取二维码…</span>
<button class="scan-btn" onclick="restartWechatScan()" style="display:none" id="scanQrRefreshBtn">刷新</button>
</div>
</div>
<p class="qr-title">微信扫码登录</p>
<p class="qr-desc">使用微信「扫一扫」,扫码后在<strong>温新云小程序</strong>中点击确认<br />即可完成登录</p>
<p class="qr-note">二维码 5 分钟内有效,过期可点击刷新</p>
</div>
</div>
{{end}}
<!-- 第三方登录(未配置时点击提示即将上线) -->
<div class="third-area" id="thirdArea">
<div class="divider"><span>其他登录方式</span></div>
<div class="third-list" id="thirdList">
<button class="third-btn" type="button" data-third-provider="wechat" onclick="thirdLogin('wechat', '微信')">
<span class="third-icon is-wechat">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 5h13v9H9l-4 4z"/><path d="M9 9h.01M13 9h.01"/>
</svg>
</span>
微信登录
</button>
<button class="third-btn" type="button" data-third-provider="qq" onclick="thirdLogin('qq', 'QQ')">
<span class="third-icon is-qq">QQ</span>
QQ 登录
</button>
</div>
</div>
{{end}}{{/* end LoginDisabled */}}
{{end}}
</div>
<div class="form-foot">
<span>© 2026 云泽 版权所有</span>
<button id="cacheClearBtn" class="cache-clear-btn" type="button" onclick="clearSiteCache()" title="仅清除本站在当前浏览器中的缓存数据">清除缓存</button>
</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, tone) {
var box = document.getElementById('error');
var text = document.getElementById('errorText');
if (!box || !text) return;
box.classList.toggle('is-info', tone === 'info');
text.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');
}
// ==================== 登录方式切换(账号密码 / 短信 / 扫码) ====================
var LOGIN_PANELS = { account: 'panel-account', sms: 'panel-sms', qr: 'panel-qr' };
function switchLoginTab(name) {
if (!LOGIN_PANELS[name]) return;
var tabs = document.querySelectorAll('.login-tab');
Array.prototype.forEach.call(tabs, function (tab) {
tab.classList.toggle('active', tab.getAttribute('data-tab') === name);
});
Object.keys(LOGIN_PANELS).forEach(function (key) {
var panel = document.getElementById(LOGIN_PANELS[key]);
if (panel) panel.classList.toggle('active', key === name);
});
hideError();
// 扫码 tab 才拉二维码,离开则停止轮询
if (name === 'qr') restartWechatScan(); else stopWechatScan();
}
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();
// 已开通的第三方登录方式:记录状态,供静态 QQ / 微信入口判断是否可用
var thirdEnabled = {};
var THIRD_STATIC = { wechat: 1, qq: 1 };
function loadThirdProviders() {
var box = document.getElementById('thirdList');
if (!box) return;
fetch('/auth/third/list')
.then(function (r) { return r.json(); })
.then(function (res) {
var list = (res && res.data) || [];
thirdEnabled = {};
// 清掉上一轮动态追加的按钮(静态 QQ / 微信入口保留)
Array.prototype.slice.call(box.querySelectorAll('[data-dynamic="1"]')).forEach(function (n) {
if (n.parentNode) n.parentNode.removeChild(n);
});
list.forEach(function (item) {
if (!item || !item.enabled) return;
thirdEnabled[item.provider] = true;
if (THIRD_STATIC[item.provider]) return;
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'third-btn';
btn.setAttribute('data-dynamic', '1');
var icon = document.createElement('span');
icon.className = 'third-icon is-generic';
icon.textContent = (item.name || '?').slice(0, 1);
btn.appendChild(icon);
btn.appendChild(document.createTextNode(item.name || item.provider));
btn.onclick = function () { startThird(item.provider); };
box.appendChild(btn);
});
// 未开通的入口:保留样式,悬停提示即将上线,点击给中性提示
Array.prototype.forEach.call(box.querySelectorAll('[data-third-provider]'), function (btn) {
var provider = btn.getAttribute('data-third-provider');
btn.title = thirdEnabled[provider] ? '' : '即将上线';
});
})
.catch(function () { /* 静默失败,不影响其他登录方式 */ });
}
function thirdLogin(provider, label) {
if (thirdEnabled[provider]) { startThird(provider); return; }
showError((label || provider) + '登录即将上线,敬请期待', 'info');
}
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 sendSmsCode() {
var input = document.getElementById('smsMobile');
var mobile = input ? input.value.trim() : '';
if (!/^1[3-9]\d{9}$/.test(mobile)) { showError('请输入正确的手机号'); return; }
showError('短信验证码服务即将上线,请先使用账号密码登录', 'info');
}
function submitSmsLogin(e) {
e.preventDefault();
hideError();
var mobileEl = document.getElementById('smsMobile');
var codeEl = document.getElementById('smsCode');
var mobile = mobileEl ? mobileEl.value.trim() : '';
var code = codeEl ? codeEl.value.trim() : '';
if (!/^1[3-9]\d{9}$/.test(mobile)) { showError('请输入正确的手机号'); return false; }
if (!code) { showError('请输入验证码'); return false; }
showError('手机号验证码登录即将上线,请先使用账号密码登录', 'info');
return false;
}
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; }
// 会话已建立但确实没有回跳地址:明确提示,绝不再跳回登录页造成死循环
showError('登录成功,但未获取到回跳地址,请从业务系统重新进入');
setBtnLoading(false);
})
.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; }
showError('已进入所选企业,但未获取到回跳地址,请从业务系统重新进入');
})
.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);
});
}
/**
* 清除本站在当前浏览器里的全部缓存数据。
*
* localStorage / sessionStorage / document.cookie / Cache Storage /
* IndexedDB / Service Worker 都受同源策略约束,只会作用于认证中心自己的
* origin,不会影响 back.yunzer.cn 等其它站点。
* HttpOnly 的 yz_sid 前端读不到,交给 /auth/clear-site-data 的
* Clear-Site-Data 响应头与 Set-Cookie 一起清除。
*/
function clearSiteCache() {
var btn = document.getElementById('cacheClearBtn');
if (btn) { btn.disabled = true; btn.textContent = '清除中...'; }
// 1. Web Storage(记住账号、各类前端状态)
try {
localStorage.clear();
sessionStorage.clear();
} catch (e) { /* 隐私模式下存储可能不可访问 */ }
// 2. 当前站点可写的 Cookie。只针对本站 host:不尝试 .yunzer.cn 等父域,
// 否则会连带清掉 back.yunzer.cn / platform.yunzer.cn 等兄弟站点的数据。
// 两次删除分别覆盖 host-only cookie 与显式 Domain=当前 host 的 cookie。
try {
var cookieNames = (document.cookie || '').split(';').map(function (item) {
return item.split('=')[0].trim();
}).filter(Boolean);
var host = window.location.hostname;
var expired = '=; Max-Age=0; Path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
cookieNames.forEach(function (name) {
document.cookie = name + expired;
document.cookie = name + expired + '; Domain=' + host;
});
} catch (e) { /* ignore */ }
var tasks = [];
// 3. Cache Storage(离线资源 / 静态资源缓存)
if (window.caches && caches.keys) {
tasks.push(caches.keys().then(function (keys) {
return Promise.all((keys || []).map(function (key) { return caches.delete(key); }));
}).catch(function () {}));
}
// 4. Service Worker 注册
if (navigator.serviceWorker && navigator.serviceWorker.getRegistrations) {
tasks.push(navigator.serviceWorker.getRegistrations().then(function (regs) {
return Promise.all((regs || []).map(function (reg) { return reg.unregister(); }));
}).catch(function () {}));
}
// 5. IndexedDB(databases() 在部分浏览器不可用,跳过即可)
if (window.indexedDB && indexedDB.databases) {
tasks.push(indexedDB.databases().then(function (dbs) {
return Promise.all((dbs || []).map(function (db) {
return new Promise(function (resolve) {
if (!db || !db.name) { resolve(); return; }
var done = false;
var finish = function () { if (!done) { done = true; resolve(); } };
var req = indexedDB.deleteDatabase(db.name);
req.onsuccess = req.onerror = req.onblocked = finish;
// 其它标签页占用数据库时 onblocked 可能不触发,兜底避免卡住
setTimeout(finish, 1500);
});
}));
}).catch(function () {}));
}
// 6. 后端清 HttpOnly 会话 Cookie,并让浏览器按 origin 清 HTTP 缓存与存储;
// 服务尚未部署新端点(404)或接口异常时退回已有登出接口,
// 至少保证会话 Cookie 被清除;整体超时也不阻断前面的前端清理结果
var legacyClear = function () {
return fetch('/auth/logout', { method: 'POST', credentials: 'same-origin', cache: 'no-store' })
.catch(function () {});
};
var clearRequest = fetch('/auth/clear-site-data', {
method: 'POST',
credentials: 'same-origin',
cache: 'no-store'
}).then(function (res) {
if (res.ok) return;
return legacyClear();
}).catch(legacyClear);
tasks.push(Promise.race([
clearRequest,
new Promise(function (resolve) { setTimeout(resolve, 5000); })
]));
Promise.all(tasks).then(function () {
// 保留 client_id / redirect,清完直接从登录页重新开始
window.location.replace('/auth/login?client_id=' + encodeURIComponent(CLIENT_ID) +
(REDIRECT ? '&redirect=' + encodeURIComponent(REDIRECT) : ''));
});
}
// ==================== 微信扫码登录(扫码 → 小程序内确认;已接 scan/start、scan/status) ====================
var scanScene = '';
var scanExpireAt = 0;
var scanPollTimer = null;
// 显隐由 switchLoginTab 控制,切换 tab 时自动启停轮询
function stopWechatScan() {
if (scanPollTimer) { clearInterval(scanPollTimer); scanPollTimer = null; }
}
function setScanQrMask(text, showRefresh) {
var img = document.getElementById('scanQrImg');
var mask = document.getElementById('scanQrMask');
img.style.display = 'none';
mask.style.display = 'flex';
document.getElementById('scanQrMaskText').textContent = text || '';
document.getElementById('scanQrRefreshBtn').style.display = showRefresh ? 'block' : 'none';
}
function restartWechatScan() {
stopWechatScan();
setScanQrMask('获取中…', false);
fetch('/auth/wechat/scan/start', { method: 'POST' })
.then(function (r) { return r.json(); })
.then(function (res) {
if (res.code !== 200) {
setScanQrMask('获取失败', true);
return;
}
var d = res.data || {};
scanScene = d.scene || '';
var qr = d.qrcode_url || d.follow_qrcode || '';
if (qr) {
var img = document.getElementById('scanQrImg');
img.src = qr;
img.style.display = 'block';
document.getElementById('scanQrMask').style.display = 'none';
} else {
setScanQrMask('暂无二维码', true);
}
// 回退原因只写控制台,不在页面展示
if (d.notice && window.console) console.warn('[wechat-scan]', d.notice);
scanPollTimer = setInterval(pollWechatScan, 3000);
})
.catch(function () {
setScanQrMask('网络异常', true);
});
}
function pollWechatScan() {
if (!scanScene) return;
fetch('/auth/wechat/scan/status?scene=' + encodeURIComponent(scanScene) +
'&client_id=' + encodeURIComponent(CLIENT_ID) +
'&redirect=' + encodeURIComponent(REDIRECT))
.then(function (r) { return r.json(); })
.then(function (res) {
var d = (res && res.data) || {};
if (Number(d.status) === 1) {
stopWechatScan();
window.location.href = d.redirect || ('/auth/login?client_id=' + encodeURIComponent(CLIENT_ID));
} else if (Number(d.status) === 2) {
stopWechatScan();
setScanQrMask('已过期', true);
}
})
.catch(function () { /* 下个周期重试 */ });
}
</script>
</body>
</html>