This commit is contained in:
+2645
-412
File diff suppressed because it is too large
Load Diff
+797
-116
@@ -3,166 +3,847 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="data:,">
|
||||
<meta name="theme-color" content="#0b1220">
|
||||
<title>登录 - 数富AI</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--auth-bg: #0b1220;
|
||||
--auth-surface: #141e31;
|
||||
--auth-surface-raised: #1a2740;
|
||||
--auth-border: rgba(148, 163, 184, 0.2);
|
||||
--auth-border-strong: rgba(148, 163, 184, 0.34);
|
||||
--auth-text: #f1f5f9;
|
||||
--auth-text-muted: #b8c4d8;
|
||||
--auth-text-subtle: #8795ad;
|
||||
--auth-primary: #818cf8;
|
||||
--auth-primary-strong: #a5b4fc;
|
||||
--auth-success: #4ade80;
|
||||
--auth-danger: #fb7185;
|
||||
--auth-radius: 18px;
|
||||
--auth-fast: 160ms;
|
||||
--auth-base: 220ms;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
html {
|
||||
min-width: 320px;
|
||||
min-height: 100%;
|
||||
background: var(--auth-bg);
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", sans-serif;
|
||||
background: #d8e4ec;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
font-family: Inter, "SF Pro Display", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--auth-text);
|
||||
background:
|
||||
radial-gradient(circle at 8% 0%, rgba(99, 102, 241, 0.19), transparent 32rem),
|
||||
radial-gradient(circle at 92% 100%, rgba(34, 197, 94, 0.06), transparent 28rem),
|
||||
var(--auth-bg);
|
||||
}
|
||||
|
||||
button,
|
||||
input { font: inherit; }
|
||||
|
||||
a { color: inherit; }
|
||||
|
||||
.auth-skip-link {
|
||||
position: fixed;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
z-index: 20;
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
background: var(--auth-primary);
|
||||
color: #0b1220;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
transform: translateY(-160%);
|
||||
transition: transform var(--auth-fast) ease;
|
||||
}
|
||||
|
||||
.auth-skip-link:focus { transform: translateY(0); }
|
||||
|
||||
.auth-page {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(420px, 0.9fr) minmax(420px, 1.1fr);
|
||||
}
|
||||
|
||||
.auth-visual {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 100vh;
|
||||
padding: 36px clamp(32px, 5vw, 84px) 34px;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid rgba(148, 163, 184, 0.13);
|
||||
background:
|
||||
linear-gradient(160deg, rgba(16, 26, 46, 0.92), rgba(8, 15, 29, 0.98)),
|
||||
radial-gradient(circle at 20% 8%, rgba(129, 140, 248, 0.18), transparent 28rem);
|
||||
}
|
||||
|
||||
.auth-visual::before,
|
||||
.auth-visual::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.auth-visual::before {
|
||||
inset: 0;
|
||||
opacity: 0.24;
|
||||
background-image: linear-gradient(rgba(148, 163, 184, 0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px);
|
||||
background-size: 42px 42px;
|
||||
mask-image: linear-gradient(to bottom, black, transparent 78%);
|
||||
}
|
||||
|
||||
.auth-visual::after {
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
right: -150px;
|
||||
bottom: -160px;
|
||||
border: 1px solid rgba(129, 140, 248, 0.18);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 32px rgba(129, 140, 248, 0.035), 0 0 0 64px rgba(129, 140, 248, 0.025);
|
||||
}
|
||||
|
||||
.auth-visual-content,
|
||||
.auth-visual-footer { position: relative; z-index: 1; }
|
||||
|
||||
.auth-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: fit-content;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-brand-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border: 1px solid rgba(165, 180, 252, 0.28);
|
||||
border-radius: 13px;
|
||||
background: linear-gradient(135deg, #818cf8, #4f46e5);
|
||||
color: #eef2ff;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 10px 28px rgba(79, 70, 229, 0.34);
|
||||
}
|
||||
.header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 56px;
|
||||
|
||||
.auth-brand-copy { display: grid; gap: 1px; }
|
||||
|
||||
.auth-brand-name {
|
||||
color: #f8fafc;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.auth-brand-sub {
|
||||
color: var(--auth-text-subtle);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.auth-visual-content {
|
||||
max-width: 520px;
|
||||
margin: auto 0;
|
||||
padding: 72px 0 96px;
|
||||
}
|
||||
|
||||
.auth-visual-copy {
|
||||
padding-top: 72px;
|
||||
}
|
||||
|
||||
.auth-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--auth-primary-strong);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.8px;
|
||||
}
|
||||
|
||||
.auth-kicker::before {
|
||||
content: "";
|
||||
width: 24px;
|
||||
height: 1px;
|
||||
background: var(--auth-primary);
|
||||
}
|
||||
|
||||
.auth-visual-title {
|
||||
max-width: 560px;
|
||||
margin: 18px 0 18px;
|
||||
color: #f8fafc;
|
||||
font-size: clamp(30px, 4vw, 52px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -1.8px;
|
||||
line-height: 1.12;
|
||||
}
|
||||
|
||||
.auth-visual-description {
|
||||
max-width: 470px;
|
||||
margin: 0;
|
||||
color: var(--auth-text-muted);
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.auth-feature-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 34px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.auth-feature-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: #dbe4f1;
|
||||
}
|
||||
|
||||
.auth-feature-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 30px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid rgba(129, 140, 248, 0.25);
|
||||
border-radius: 9px;
|
||||
background: rgba(129, 140, 248, 0.11);
|
||||
color: var(--auth-primary-strong);
|
||||
}
|
||||
|
||||
.auth-feature-icon svg { width: 16px; height: 16px; }
|
||||
|
||||
.auth-visual-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
background: rgba(255,255,255,0.5);
|
||||
gap: 16px;
|
||||
color: #72819a;
|
||||
font-size: 12px;
|
||||
}
|
||||
.header-title { font-size: 18px; font-weight: 600; color: #333; }
|
||||
.login-box {
|
||||
background: #fff;
|
||||
border: 1px solid #b8d4e3;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
|
||||
.auth-system-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: #86efac;
|
||||
}
|
||||
|
||||
.auth-system-status::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--auth-success);
|
||||
box-shadow: 0 0 0 4px rgba(74, 222, 128, 0.12);
|
||||
}
|
||||
|
||||
.auth-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
padding: 40px clamp(24px, 6vw, 96px);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: min(100%, 452px);
|
||||
padding: clamp(28px, 4vw, 48px);
|
||||
border: 1px solid var(--auth-border);
|
||||
border-radius: 22px;
|
||||
background: linear-gradient(145deg, rgba(20, 30, 49, 0.98), rgba(16, 26, 46, 0.96));
|
||||
box-shadow: 0 26px 70px -38px rgba(2, 6, 23, 0.95);
|
||||
}
|
||||
|
||||
.login-card-header { margin-bottom: 30px; }
|
||||
|
||||
.login-card-eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: var(--auth-text-subtle);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1.2px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
margin: 0;
|
||||
color: var(--auth-text);
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.8px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.login-subtitle {
|
||||
margin: 10px 0 0;
|
||||
color: var(--auth-text-muted);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.form-group { margin-bottom: 20px; }
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
margin-bottom: 8px;
|
||||
color: var(--auth-text-muted);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input-shell { position: relative; }
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 14px;
|
||||
display: inline-flex;
|
||||
color: #8190a8;
|
||||
pointer-events: none;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.input-icon svg { width: 18px; height: 18px; }
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #b8d4e3;
|
||||
border-radius: 8px;
|
||||
min-height: 48px;
|
||||
padding: 12px 52px 12px 44px;
|
||||
border: 1px solid var(--auth-border);
|
||||
border-radius: 12px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
background: #fff;
|
||||
background: rgba(11, 18, 32, 0.72);
|
||||
color: var(--auth-text);
|
||||
font-size: 14px;
|
||||
color-scheme: dark;
|
||||
transition: border-color var(--auth-fast) ease, box-shadow var(--auth-fast) ease, background var(--auth-fast) ease;
|
||||
}
|
||||
|
||||
.form-group input:hover { border-color: var(--auth-border-strong); }
|
||||
|
||||
.form-group input:focus {
|
||||
border-color: #3498db;
|
||||
border-color: var(--auth-primary);
|
||||
background: rgba(11, 18, 32, 0.92);
|
||||
box-shadow: 0 0 0 4px rgba(129, 140, 248, 0.15);
|
||||
}
|
||||
|
||||
.form-group input::placeholder { color: #71809a; }
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
color: #8190a8;
|
||||
cursor: pointer;
|
||||
transform: translateY(-50%);
|
||||
transition: background var(--auth-fast) ease, color var(--auth-fast) ease;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
background: rgba(129, 140, 248, 0.12);
|
||||
color: var(--auth-primary-strong);
|
||||
}
|
||||
|
||||
.password-toggle svg { width: 18px; height: 18px; }
|
||||
|
||||
.error-msg {
|
||||
color: #e74c3c;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 9px;
|
||||
margin: -4px 0 18px;
|
||||
padding: 11px 12px;
|
||||
border: 1px solid rgba(251, 113, 133, 0.28);
|
||||
border-radius: 11px;
|
||||
background: rgba(251, 113, 133, 0.1);
|
||||
color: #fda4af;
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.error-msg::before {
|
||||
content: "!";
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 50%;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 12px 18px;
|
||||
border: 1px solid rgba(165, 180, 252, 0.32);
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #818cf8, #6366f1);
|
||||
color: #0b1220;
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
font-weight: 800;
|
||||
box-shadow: 0 12px 24px -16px rgba(129, 140, 248, 0.95);
|
||||
transition: transform var(--auth-fast) ease, box-shadow var(--auth-fast) ease, background var(--auth-fast) ease, opacity var(--auth-fast) ease;
|
||||
}
|
||||
|
||||
.btn-login:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #a5b4fc, #818cf8);
|
||||
box-shadow: 0 16px 28px -15px rgba(129, 140, 248, 0.98);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-login:active:not(:disabled) { transform: translateY(1px) scale(0.99); }
|
||||
|
||||
.btn-login:disabled { cursor: wait; opacity: 0.7; }
|
||||
|
||||
.btn-login-spinner {
|
||||
display: none;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgba(11, 18, 32, 0.28);
|
||||
border-top-color: #0b1220;
|
||||
border-radius: 50%;
|
||||
animation: login-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.btn-login.is-loading .btn-login-spinner { display: inline-block; }
|
||||
|
||||
@keyframes login-spin { to { transform: rotate(360deg); } }
|
||||
|
||||
.login-security-note {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 9px;
|
||||
margin: 22px 0 0;
|
||||
color: var(--auth-text-subtle);
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.login-security-note svg {
|
||||
flex: 0 0 auto;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 1px;
|
||||
color: var(--auth-success);
|
||||
}
|
||||
|
||||
.login-card-footer {
|
||||
margin-top: 34px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid rgba(148, 163, 184, 0.13);
|
||||
color: #72819a;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--auth-primary-strong);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.auth-page { display: block; }
|
||||
.auth-visual {
|
||||
min-height: auto;
|
||||
padding: 22px 24px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, 0.13);
|
||||
}
|
||||
.auth-visual-content { display: block; max-width: none; margin: 0; padding: 0; }
|
||||
.auth-visual-copy { display: none; }
|
||||
.auth-visual-footer { display: none; }
|
||||
.auth-content { min-height: calc(100vh - 87px); padding: 32px 24px 44px; }
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.auth-visual { padding: 18px 16px; }
|
||||
.auth-content { padding: 24px 14px 32px; }
|
||||
.login-card { padding: 26px 20px; border-radius: 18px; }
|
||||
.login-title { font-size: 27px; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
scroll-behavior: auto !important;
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== 登录页莫兰迪亮色主题 ===== */
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--auth-bg: #f2f4f1;
|
||||
--auth-surface: #ffffff;
|
||||
--auth-surface-raised: #f8faf8;
|
||||
--auth-border: #dbe3dd;
|
||||
--auth-border-strong: #b9c9bd;
|
||||
--auth-text: #29362f;
|
||||
--auth-text-muted: #5d6c63;
|
||||
--auth-text-subtle: #7f8d84;
|
||||
--auth-primary: #607a6d;
|
||||
--auth-primary-strong: #456052;
|
||||
--auth-success: #4e8068;
|
||||
--auth-danger: #a9545d;
|
||||
}
|
||||
|
||||
html { background: var(--auth-bg); }
|
||||
|
||||
body {
|
||||
color: var(--auth-text);
|
||||
background:
|
||||
radial-gradient(circle at 8% 0%, rgba(177, 198, 185, 0.34), transparent 34rem),
|
||||
radial-gradient(circle at 96% 100%, rgba(213, 190, 178, 0.2), transparent 28rem),
|
||||
var(--auth-bg);
|
||||
}
|
||||
|
||||
.auth-skip-link {
|
||||
background: var(--auth-primary);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.auth-visual {
|
||||
border-right-color: #d5ded7;
|
||||
background:
|
||||
linear-gradient(160deg, rgba(232, 239, 234, 0.96), rgba(246, 248, 245, 0.98)),
|
||||
radial-gradient(circle at 20% 8%, rgba(127, 153, 138, 0.16), transparent 28rem);
|
||||
}
|
||||
|
||||
.auth-visual::before {
|
||||
opacity: 0.32;
|
||||
background-image: linear-gradient(rgba(96, 122, 109, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(96, 122, 109, 0.1) 1px, transparent 1px);
|
||||
}
|
||||
|
||||
.auth-visual::after {
|
||||
border-color: rgba(96, 122, 109, 0.22);
|
||||
box-shadow: 0 0 0 32px rgba(96, 122, 109, 0.055), 0 0 0 64px rgba(96, 122, 109, 0.035);
|
||||
}
|
||||
|
||||
.auth-brand-mark {
|
||||
border-color: rgba(69, 96, 82, 0.18);
|
||||
background: linear-gradient(135deg, #829b8b, #607a6d);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 10px 24px rgba(96, 122, 109, 0.24);
|
||||
}
|
||||
|
||||
.auth-brand-name,
|
||||
.auth-visual-title { color: var(--auth-text); }
|
||||
.auth-brand-sub { color: #76857b; }
|
||||
.auth-kicker { color: var(--auth-primary-strong); }
|
||||
.auth-kicker::before { background: var(--auth-primary); }
|
||||
.auth-visual-description { color: #607069; }
|
||||
.auth-feature-item { color: #46564d; }
|
||||
.auth-feature-icon { border-color: #c5d5c9; background: #edf3ee; color: var(--auth-primary-strong); }
|
||||
.auth-visual-footer { color: #77857d; }
|
||||
.auth-system-status { color: #3f7258; }
|
||||
.auth-system-status::before { background: var(--auth-success); box-shadow: 0 0 0 4px rgba(78, 128, 104, 0.13); }
|
||||
|
||||
.auth-content {
|
||||
background: rgba(248, 250, 248, 0.45);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
border-color: var(--auth-border);
|
||||
background: linear-gradient(145deg, #ffffff, #f9fbf9);
|
||||
box-shadow: 0 26px 70px -38px rgba(60, 77, 67, 0.34);
|
||||
}
|
||||
|
||||
.login-card-eyebrow { color: #718078; }
|
||||
.login-title { color: var(--auth-text); }
|
||||
.login-subtitle { color: var(--auth-text-muted); }
|
||||
.form-group label { color: var(--auth-text-muted); }
|
||||
.input-icon { color: #82938a; }
|
||||
|
||||
.form-group input {
|
||||
background: #f7faf7;
|
||||
border-color: #cfdad2;
|
||||
color: var(--auth-text);
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.form-group input:hover { border-color: #aebfb3; }
|
||||
.form-group input:focus { background: #ffffff; border-color: #6f8b7b; box-shadow: 0 0 0 4px rgba(111, 139, 123, 0.16); }
|
||||
.form-group input::placeholder { color: #87958c; }
|
||||
|
||||
.password-toggle { color: #82938a; }
|
||||
.password-toggle:hover { background: #edf4ee; color: var(--auth-primary-strong); }
|
||||
|
||||
.error-msg { border-color: #e4c2c5; background: #f8ebeb; color: #91474f; }
|
||||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border-color: #7d9988;
|
||||
background: linear-gradient(135deg, #718b7c, #607a6d);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 12px 24px -16px rgba(96, 122, 109, 0.82);
|
||||
}
|
||||
.btn-login:hover {
|
||||
opacity: 0.9;
|
||||
|
||||
.btn-login:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #829b8b, #6b8577);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 16px 28px -15px rgba(96, 122, 109, 0.86);
|
||||
}
|
||||
.btn-login:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
|
||||
.btn-login-spinner { border-color: rgba(255, 255, 255, 0.35); border-top-color: #ffffff; }
|
||||
.login-security-note { color: #718078; }
|
||||
.login-security-note svg { color: var(--auth-success); }
|
||||
.login-card-footer { border-top-color: #dce5de; color: #77857d; }
|
||||
:focus-visible { outline-color: #607a6d; }
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.auth-visual { border-bottom-color: #d5ded7; }
|
||||
}
|
||||
|
||||
|
||||
/* ===== 登录页统一蓝白色调 ===== */
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--auth-bg: #f4f7fb;
|
||||
--auth-surface: #ffffff;
|
||||
--auth-surface-raised: #f9fbfd;
|
||||
--auth-border: #d8e3ee;
|
||||
--auth-border-strong: #b7c9db;
|
||||
--auth-text: #24384d;
|
||||
--auth-text-muted: #5b6f83;
|
||||
--auth-text-subtle: #8293a5;
|
||||
--auth-primary: #4f78a5;
|
||||
--auth-primary-strong: #2f5d8b;
|
||||
--auth-success: #4e806d;
|
||||
--auth-danger: #b35f6a;
|
||||
}
|
||||
|
||||
html, body { background: var(--auth-bg); }
|
||||
body {
|
||||
color: var(--auth-text);
|
||||
background:
|
||||
radial-gradient(circle at 8% 0%, rgba(178, 205, 229, 0.32), transparent 34rem),
|
||||
radial-gradient(circle at 96% 100%, rgba(214, 226, 239, 0.28), transparent 28rem),
|
||||
var(--auth-bg);
|
||||
}
|
||||
.auth-skip-link { background: var(--auth-primary); color: #ffffff; }
|
||||
.auth-visual {
|
||||
border-right-color: #d4e0eb;
|
||||
background:
|
||||
linear-gradient(160deg, rgba(232, 240, 248, 0.97), rgba(248, 251, 254, 0.99)),
|
||||
radial-gradient(circle at 20% 8%, rgba(112, 148, 186, 0.16), transparent 28rem);
|
||||
}
|
||||
.auth-visual::before { opacity: 0.3; background-image: linear-gradient(rgba(79, 120, 165, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(79, 120, 165, 0.1) 1px, transparent 1px); }
|
||||
.auth-visual::after { border-color: rgba(79, 120, 165, 0.22); box-shadow: 0 0 0 32px rgba(79, 120, 165, 0.055), 0 0 0 64px rgba(79, 120, 165, 0.035); }
|
||||
.auth-brand-mark { border-color: rgba(47, 93, 139, 0.18); background: linear-gradient(135deg, #7094ba, #4f78a5); color: #ffffff; box-shadow: 0 10px 24px rgba(79, 120, 165, 0.24); }
|
||||
.auth-brand-name, .auth-visual-title { color: var(--auth-text); }
|
||||
.auth-brand-sub { color: #77899b; }
|
||||
.auth-kicker { color: var(--auth-primary-strong); }
|
||||
.auth-kicker::before { background: var(--auth-primary); }
|
||||
.auth-visual-description { color: #60748a; }
|
||||
.auth-feature-item { color: #465d73; }
|
||||
.auth-feature-icon { border-color: #c2d3e3; background: #edf5fb; color: var(--auth-primary-strong); }
|
||||
.auth-visual-footer { color: #778b9f; }
|
||||
.auth-system-status { color: #3d7158; }
|
||||
.auth-system-status::before { background: var(--auth-success); box-shadow: 0 0 0 4px rgba(78, 128, 104, 0.13); }
|
||||
.auth-content { background: rgba(249, 251, 253, 0.5); }
|
||||
.login-card { border-color: var(--auth-border); background: linear-gradient(145deg, #ffffff, #f9fbfd); box-shadow: 0 26px 70px -38px rgba(39, 67, 94, 0.34); }
|
||||
.login-card-eyebrow { color: #71859a; }
|
||||
.login-title { color: var(--auth-text); }
|
||||
.login-subtitle, .form-group label { color: var(--auth-text-muted); }
|
||||
.input-icon, .password-toggle { color: #8298ad; }
|
||||
.form-group input { background: #f8fbfd; border-color: #cbd9e6; color: var(--auth-text); color-scheme: light; }
|
||||
.form-group input:hover { border-color: #9fb7cd; }
|
||||
.form-group input:focus { background: #ffffff; border-color: #5f85ad; box-shadow: 0 0 0 4px rgba(95, 133, 173, 0.16); }
|
||||
.form-group input::placeholder { color: #8293a5; }
|
||||
.password-toggle:hover { background: #edf5fb; color: var(--auth-primary-strong); }
|
||||
.error-msg { border-color: #e4c2c5; background: #f8ebeb; color: #91474f; }
|
||||
.btn-login { border-color: #7196ba; background: linear-gradient(135deg, #5f85ad, #4f78a5); color: #ffffff; box-shadow: 0 12px 24px -16px rgba(79, 120, 165, 0.82); }
|
||||
.btn-login:hover:not(:disabled) { background: linear-gradient(135deg, #7094ba, #5d83ac); color: #ffffff; box-shadow: 0 16px 28px -15px rgba(79, 120, 165, 0.86); }
|
||||
.btn-login-spinner { border-color: rgba(255,255,255,0.35); border-top-color: #ffffff; }
|
||||
.login-security-note { color: #71859a; }
|
||||
.login-security-note svg { color: var(--auth-success); }
|
||||
.login-card-footer { border-top-color: #dce5ee; color: #778b9f; }
|
||||
:focus-visible { outline-color: #4f78a5; }
|
||||
@media (max-width: 900px) { .auth-visual { border-bottom-color: #d4e0eb; } }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<span class="header-title">数富AI</span>
|
||||
</header>
|
||||
<a class="auth-skip-link" href="#loginMain">跳转到登录表单</a>
|
||||
<div class="auth-page">
|
||||
<aside class="auth-visual" aria-label="数富AI产品信息">
|
||||
<div class="auth-visual-content">
|
||||
<a class="auth-brand" href="/login" aria-label="数富AI 登录页">
|
||||
<span class="auth-brand-mark" aria-hidden="true">数</span>
|
||||
<span class="auth-brand-copy">
|
||||
<span class="auth-brand-name">数富AI</span>
|
||||
<span class="auth-brand-sub">电商运营管理后台</span>
|
||||
</span>
|
||||
</a>
|
||||
<div class="auth-visual-copy">
|
||||
<span class="auth-kicker">数富AI · 运营工作台</span>
|
||||
<h2 class="auth-visual-title">让每一次运营动作,都有清晰的工作流。</h2>
|
||||
<p class="auth-visual-description">统一管理数据、店铺、任务与版本,让团队在一个可靠的运营工作台中快速协作。</p>
|
||||
<ul class="auth-feature-list" aria-label="工作台能力">
|
||||
<li class="auth-feature-item">
|
||||
<span class="auth-feature-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 4 7v5c0 4.5 3.4 7.7 8 9 4.6-1.3 8-4.5 8-9V7l-8-4Z"></path><path d="m8.5 12 2.2 2.2 4.8-5"></path></svg></span>
|
||||
<span>权限隔离,操作边界清晰可控</span>
|
||||
</li>
|
||||
<li class="auth-feature-item">
|
||||
<span class="auth-feature-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v4l3 2"></path><circle cx="12" cy="12" r="9"></circle><path d="M3 4v5h5"></path><path d="M3.5 9A9 9 0 0 1 19 5.5"></path></svg></span>
|
||||
<span>任务状态,进度反馈及时透明</span>
|
||||
</li>
|
||||
<li class="auth-feature-item">
|
||||
<span class="auth-feature-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5"></path><path d="M4 19h16"></path><path d="m7 15 3-4 3 2 4-6"></path><path d="M17 7h3v3"></path></svg></span>
|
||||
<span>数据工具,支撑日常电商运营</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="auth-visual-footer">
|
||||
<span>数富AI · 管理控制台</span>
|
||||
<span class="auth-system-status">本地服务已就绪</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="login-box">
|
||||
<h1 class="login-title">登录</h1>
|
||||
{% if error %}
|
||||
<p class="error-msg">{{ error }}</p>
|
||||
{% endif %}
|
||||
<form id="loginForm" method="POST" action="/login">
|
||||
<div class="form-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" name="username" placeholder="请输入用户名" required autofocus>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>密码</label>
|
||||
<input type="password" name="password" placeholder="请输入密码" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-login" id="btnLogin">登录</button>
|
||||
</form>
|
||||
<main class="auth-content" id="loginMain">
|
||||
<section class="login-card" aria-labelledby="loginTitle">
|
||||
<header class="login-card-header">
|
||||
<p class="login-card-eyebrow">欢迎回来</p>
|
||||
<h1 class="login-title" id="loginTitle">登录工作台</h1>
|
||||
<p class="login-subtitle">使用管理员账号进入数富AI运营后台。</p>
|
||||
</header>
|
||||
<form id="loginForm" method="POST" action="/login" novalidate>
|
||||
{% if error %}
|
||||
<p class="error-msg" id="loginError" role="alert" aria-live="assertive">{{ error }}</p>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label for="loginUsername">用户名</label>
|
||||
<div class="input-shell">
|
||||
<span class="input-icon" aria-hidden="true"><svg 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"></circle><path d="M4 21a8 8 0 0 1 16 0"></path></svg></span>
|
||||
<input type="text" id="loginUsername" name="username" autocomplete="username" placeholder="请输入用户名" required autofocus>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="loginPassword">密码</label>
|
||||
<div class="input-shell">
|
||||
<span class="input-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="10" x="5" y="11" rx="2"></rect><path d="M8 11V7a4 4 0 0 1 8 0v4"></path></svg></span>
|
||||
<input type="password" id="loginPassword" name="password" autocomplete="current-password" placeholder="请输入密码" required>
|
||||
<button type="button" class="password-toggle" id="togglePassword" aria-label="显示密码" aria-pressed="false" title="显示密码">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z"></path><circle cx="12" cy="12" r="2.5"></circle></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-login" id="btnLogin" aria-busy="false">
|
||||
<span class="btn-login-label">登录</span>
|
||||
<span class="btn-login-spinner" aria-hidden="true"></span>
|
||||
</button>
|
||||
</form>
|
||||
<p class="login-security-note">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3 4 7v5c0 4.5 3.4 7.7 8 9 4.6-1.3 8-4.5 8-9V7l-8-4Z"></path><path d="m8.5 12 2.2 2.2 4.8-5"></path></svg>
|
||||
<span>请勿在公共设备保存账号凭据。登录状态由本地安全会话管理。</span>
|
||||
</p>
|
||||
<footer class="login-card-footer">数富AI · 电商运营管理工作台</footer>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var params = new URLSearchParams(window.location.search || '');
|
||||
if (params.get('logout') === '1' || params.get('switch') === '1') {
|
||||
return;
|
||||
}
|
||||
fetch('/api/auth/check', { credentials: 'same-origin' })
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
if (res.logged_in && res.redirect) {
|
||||
window.location.href = res.redirect;
|
||||
}
|
||||
})
|
||||
.catch(function() {});
|
||||
})();
|
||||
document.getElementById('loginForm').onsubmit = function(e) {
|
||||
e.preventDefault();
|
||||
(function () {
|
||||
var form = document.getElementById('loginForm');
|
||||
var btn = document.getElementById('btnLogin');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '登录中...';
|
||||
var form = e.target;
|
||||
var fd = new FormData(form);
|
||||
fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(res) {
|
||||
if (res.success) {
|
||||
window.location.href = res.redirect || '/admin';
|
||||
} else {
|
||||
var errEl = document.querySelector('.error-msg');
|
||||
if (!errEl) {
|
||||
errEl = document.createElement('p');
|
||||
errEl.className = 'error-msg';
|
||||
form.insertBefore(errEl, form.firstChild);
|
||||
}
|
||||
errEl.textContent = res.error || '登录失败';
|
||||
btn.disabled = false;
|
||||
btn.textContent = '登录';
|
||||
var label = btn ? btn.querySelector('.btn-login-label') : null;
|
||||
var password = document.getElementById('loginPassword');
|
||||
var togglePassword = document.getElementById('togglePassword');
|
||||
if (!form || !btn) return;
|
||||
|
||||
if (password && togglePassword) {
|
||||
togglePassword.addEventListener('click', function () {
|
||||
var visible = password.type === 'password';
|
||||
password.type = visible ? 'text' : 'password';
|
||||
togglePassword.setAttribute('aria-pressed', visible ? 'true' : 'false');
|
||||
togglePassword.setAttribute('aria-label', visible ? '隐藏密码' : '显示密码');
|
||||
togglePassword.setAttribute('title', visible ? '隐藏密码' : '显示密码');
|
||||
});
|
||||
}
|
||||
|
||||
function setError(message) {
|
||||
var errEl = document.getElementById('loginError') || document.querySelector('.error-msg');
|
||||
if (!errEl) {
|
||||
errEl = document.createElement('p');
|
||||
errEl.id = 'loginError';
|
||||
errEl.className = 'error-msg';
|
||||
errEl.setAttribute('role', 'alert');
|
||||
errEl.setAttribute('aria-live', 'assertive');
|
||||
form.insertBefore(errEl, form.firstChild);
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
form.submit();
|
||||
errEl.textContent = message || '登录失败';
|
||||
}
|
||||
|
||||
function setLoading(loading) {
|
||||
btn.disabled = loading;
|
||||
btn.classList.toggle('is-loading', loading);
|
||||
btn.setAttribute('aria-busy', loading ? 'true' : 'false');
|
||||
if (label) label.textContent = loading ? '登录中...' : '登录';
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
if (btn.disabled) return;
|
||||
setLoading(true);
|
||||
var data = new FormData(form);
|
||||
fetch(form.action, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin'
|
||||
})
|
||||
.then(function (response) {
|
||||
return response.json().catch(function () {
|
||||
return { success: false, error: '登录响应异常,请重试' };
|
||||
});
|
||||
})
|
||||
.then(function (result) {
|
||||
if (result.success) {
|
||||
window.location.href = result.redirect || '/admin';
|
||||
return;
|
||||
}
|
||||
setError(result.error || '用户名或密码错误');
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(function () {
|
||||
// API 不可用时保留原生表单提交作为兜底路径。
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user