更新日报相关功能

This commit is contained in:
hero920103 committed 2026-10-22 17:55:50 +08:00
1 parent 51fd90fc51
commit 68cdaf38fa
23 files changed
+2313 -1056

No files matched your search

+426 -203
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>统一认证中心 - 登录</title>
<title>{{.LoginTitle}} - 统一认证中心</title>
<style>
:root {
--primary: #3973ff;
@@ -14,7 +14,7 @@
--text-sub: #5b6780;
--muted: #8a94ad;
--border: #e3e9f5;
--radius: 12px;
--radius: 10px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
@@ -23,124 +23,189 @@
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%);
background: #f3f6fc;
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;
}
.login-page { display: flex; min-height: 100vh; width: 100%; }
/* ---------- 左侧品牌区 ---------- */
.auth-side {
width: 340px;
flex: 0 0 340px;
padding: 44px 34px 28px;
background: linear-gradient(160deg, #1b3b8f 0%, #2f6fe4 52%, #52a8ff 100%);
color: #fff;
.brand-panel {
position: relative;
flex: 1 1 58%;
min-height: 100vh;
padding: 64px 72px 52px;
display: flex;
flex-direction: column;
position: relative;
justify-content: center;
overflow: hidden;
background: linear-gradient(150deg, #1b3b8f 0%, #2f6fe4 52%, #52a8ff 100%);
color: #fff;
}
.auth-side::before {
.brand-panel::before {
content: '';
position: absolute;
width: 260px; height: 260px;
top: -90px; right: -110px;
width: 420px; height: 420px;
top: -150px; right: -160px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.10);
}
.auth-side::after {
.brand-panel::after {
content: '';
position: absolute;
width: 190px; height: 190px;
bottom: -70px; left: -60px;
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 { display: flex; align-items: center; gap: 12px; position: relative; z-index: 1; }
.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: 40px; height: 40px;
border-radius: 11px;
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: 22px; height: 22px; }
.brand-name { font-size: 16px; font-weight: 600; letter-spacing: .5px; }
.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; }
.side-title {
margin-top: 44px;
font-size: 26px;
font-weight: 600;
line-height: 1.45;
position: relative; z-index: 1;
.brand-title {
font-size: clamp(34px, 3.6vw, 52px);
font-weight: 700;
line-height: 1.28;
letter-spacing: 1px;
white-space: pre-line;
}
.side-desc {
margin-top: 12px;
font-size: 13.5px;
.brand-sub {
margin-top: 20px;
font-size: 16px;
line-height: 1.9;
color: rgba(255, 255, 255, 0.76);
position: relative; z-index: 1;
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); }
.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);
.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;
}
.side-foot svg { width: 13px; height: 13px; }
.brand-panel.has-bg .brand-foot { color: var(--muted); }
.brand-foot svg { width: 13px; height: 13px; flex: 0 0 auto; }
/* ---------- 右侧表单区 ---------- */
.auth-main {
flex: 1;
min-width: 0;
padding: 52px 56px 40px;
.form-panel {
position: relative;
flex: 0 0 42%;
min-width: 460px;
min-height: 100vh;
background: #fff;
display: flex;
flex-direction: column;
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-panel.active { display: block; animation: panelIn .24s ease both; }
@keyframes panelIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: none; }
}
.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); }
/* 小屏时左侧品牌区隐藏,在表单上方展示紧凑品牌头 */
.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;
text-align: center;
font-size: 12px;
color: #a3acbf;
}
.error {
display: none;
@@ -157,6 +222,8 @@
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); }
@@ -280,8 +347,8 @@
.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;
display: inline-flex; align-items: center; gap: 8px;
padding: 8px 16px;
border: 1px solid var(--border);
border-radius: 22px;
background: #fff;
@@ -296,6 +363,39 @@
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; }
@@ -333,27 +433,38 @@
.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; }
.back { text-align: center; margin-top: 18px; font-size: 13px; color: var(--text-sub); line-height: 1.7; }
/* 微信扫码登录 */
.scan-flex { display: flex; justify-content: center; }
.scan-qr {
width: 180px; height: 180px; flex: 0 0 180px;
border: 1px solid var(--border); border-radius: 10px;
/* ---------- 扫码登录 ---------- */
.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; background: #fff;
overflow: hidden;
}
.scan-qr .qr-img { width: 100%; height: 100%; object-fit: contain; }
.scan-qr .qr-mask {
display: flex; flex-direction: column; align-items: center; gap: 8px;
font-size: 12px; color: var(--muted); text-align: center; padding: 8px;
.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: 6px 16px; font-size: 12px; cursor: pointer;
border-radius: 8px; padding: 7px 18px; font-size: 12px; cursor: pointer;
}
.scan-right { display: none; }
.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;
@@ -365,56 +476,57 @@
}
.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) {
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; }
.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="glow glow-1"></div>
<div class="glow glow-2"></div>
<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="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 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="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">
<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"/>
@@ -424,7 +536,24 @@
</aside>
<!-- 右侧表单区 -->
<main class="auth-main">
<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>
@@ -462,21 +591,10 @@
<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>
<h1 class="panel-title">登录</h1>
<p class="panel-sub">请选择登录方式,进入您的工作空间</p>
</div>
{{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}}
<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"/>
@@ -484,7 +602,27 @@
<span id="errorText"></span>
</div>
<form id="loginForm" onsubmit="return submitLogin(event)" autocomplete="on">
<!-- 登录方式切换:默认账号密码 -->
<div class="login-tabs" role="tablist">
<button class="login-tab active" type="button" role="tab" data-tab="account" onclick="switchLoginTab('account')">账号密码</button>
<button class="login-tab" type="button" role="tab" data-tab="sms" onclick="switchLoginTab('sms')">短信登录</button>
<button class="login-tab" type="button" role="tab" data-tab="qr" onclick="switchLoginTab('qr')">扫码登录</button>
</div>
<!-- 账号密码登录 -->
<div class="login-panel active" 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">
@@ -524,46 +662,82 @@
</button>
</form>
<div class="back" style="margin-top:16px">还没有账号?<a class="link" href="/auth/register?client_id={{urlquery .ClientID}}&redirect={{urlquery .Redirect}}">立即注册</a></div>
<div class="third-area" id="thirdArea" style="display:none">
<div class="divider"><span>其他登录方式</span></div>
<div class="third-list" id="thirdList"></div>
<div class="back">还没有账号?<a class="link" href="/auth/register?client_id={{urlquery .ClientID}}&redirect={{urlquery .Redirect}}">立即注册</a></div>
</div>
<!-- 微信扫码登录(未认证公众号:服务号二维码 + 登录码,公众号内确认后自动登录) -->
<div class="divider"><span>其他登录方式</span></div>
<div class="third-list" style="justify-content:flex-start">
<button class="third-btn" type="button" id="wechatScanEntry" onclick="showWechatScan()">
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 4h16v12H8l-4 4z"/><path d="M8.5 9.5h.01M12 9.5h.01M15.5 9.5h.01"/>
</svg>
微信登录
</button>
<!-- 手机号验证码登录 -->
<div class="login-panel" 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>
<div id="wechatScanArea" style="display:none; margin-top:18px;">
<div class="scan-flex">
<div class="scan-qr">
<!-- 扫码登录:微信扫码 → 小程序内确认 -->
<div class="login-panel" 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>
<!-- 第三方登录(未配置时点击提示即将上线) -->
<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 class="back"><a class="link" onclick="hideWechatScan()">返回账号密码登录</a></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>
<div class="form-foot">© 2026 云泽科技 版权所有</div>
</main>
</div>
@@ -578,9 +752,12 @@
var GEETEST_SDK_LOCAL = '/static/js/gt4.js';
var geetestSDKPromise = null;
function showError(msg) {
function showError(msg, tone) {
var box = document.getElementById('error');
document.getElementById('errorText').textContent = msg;
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');
@@ -591,6 +768,24 @@
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;
@@ -682,28 +877,50 @@
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) || [];
// 微信走「公众号扫码登录」独立入口,第三方列表里不再重复展示
var enabled = list.filter(function (i) { return i.enabled && i.provider !== 'wechat'; });
var box = document.getElementById('thirdList');
box.innerHTML = ''; // 防止重复渲染
if (!enabled.length) return;
enabled.forEach(function (item) {
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.className = 'third-btn';
btn.type = 'button';
btn.textContent = item.name;
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);
});
document.getElementById('thirdArea').style.display = 'block';
// 未开通的入口:保留样式,悬停提示即将上线,点击给中性提示
Array.prototype.forEach.call(box.querySelectorAll('[data-third-provider]'), function (btn) {
var provider = btn.getAttribute('data-third-provider');
btn.title = thirdEnabled[provider] ? '' : '即将上线';
});
})
.catch(function () { /* 静默失败,不影响账号密码登录 */ });
.catch(function () { /* 静默失败,不影响其他登录方式 */ });
}
function thirdLogin(provider, label) {
if (thirdEnabled[provider]) { startThird(provider); return; }
showError((label || provider) + '登录即将上线,敬请期待', 'info');
}
function startThird(provider) {
@@ -772,6 +989,27 @@
});
}
// ==================== 手机号验证码登录(仅样式,接口待接入) ====================
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();
@@ -867,27 +1105,12 @@
});
}
// ==================== 微信扫码登录(未认证公众号:服务号二维码 + 登录码) ====================
// ==================== 微信扫码登录(扫码 → 小程序内确认;已接 scan/start、scan/status) ====================
var scanScene = '';
var scanExpireAt = 0;
var scanPollTimer = null;
function showWechatScan() {
hideError();
document.getElementById('loginForm').style.display = 'none';
document.getElementById('thirdArea').style.display = 'none';
document.getElementById('wechatScanEntry').parentElement.style.display = 'none';
document.getElementById('wechatScanArea').style.display = 'block';
restartWechatScan();
}
function hideWechatScan() {
stopWechatScan();
document.getElementById('wechatScanArea').style.display = 'none';
document.getElementById('loginForm').style.display = 'block';
document.getElementById('wechatScanEntry').parentElement.style.display = 'flex';
loadThirdProviders();
}
// 显隐由 switchLoginTab 控制,切换 tab 时自动启停轮询
function stopWechatScan() {
if (scanPollTimer) { clearInterval(scanPollTimer); scanPollTimer = null; }