feat(ui): 重做登录页并替换默认站点图标
- 登录页改为左侧品牌区 + 右侧表单卡,补字段标签、三步流程与 DCAuth 等待面板 - 新增自定义站点图标(SVG / ICO / apple-touch-icon / 192 / 512) - index.html 补齐图标、theme-color 与页面描述 - 修复 client.ts 中既有的 any 类型告警
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="theme-color" content="#0b1020" />
|
||||
<meta
|
||||
name="description"
|
||||
content="直播运营后台:直播账号、任务、自动化与数据的一站式管理平台。"
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="16x16 32x32 48x48" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<title>直播运营后台</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 992 B |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -25,7 +25,15 @@ api.interceptors.response.use(
|
||||
if (typeof detail === 'string') {
|
||||
msg = detail;
|
||||
} else if (Array.isArray(detail) && detail.length > 0) {
|
||||
msg = detail.map((d: any) => d?.msg || (typeof d === 'string' ? d : JSON.stringify(d))).join('; ');
|
||||
msg = detail
|
||||
.map((item: unknown) => {
|
||||
if (typeof item === 'string') return item;
|
||||
if (item && typeof item === 'object' && 'msg' in item) {
|
||||
return String((item as { msg?: unknown }).msg ?? '');
|
||||
}
|
||||
return JSON.stringify(item);
|
||||
})
|
||||
.join('; ');
|
||||
} else if (detail && typeof detail === 'object') {
|
||||
msg = JSON.stringify(detail);
|
||||
}
|
||||
|
||||
@@ -34,3 +34,449 @@ html[data-theme='dark'] body {
|
||||
.huya-collab-home-drop .ant-upload-btn {
|
||||
padding: 10px 12px !important;
|
||||
}
|
||||
|
||||
/* ---------- 登录页 ---------- */
|
||||
.login-page {
|
||||
--login-card-bg: rgba(255, 255, 255, 0.92);
|
||||
--login-border: rgba(15, 23, 42, 0.08);
|
||||
--login-text: #0f172a;
|
||||
--login-muted: #64748b;
|
||||
--login-step-bg: rgba(15, 23, 42, 0.03);
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(900px 520px at 12% 8%, rgba(99, 102, 241, 0.28), transparent 62%),
|
||||
radial-gradient(820px 520px at 88% 92%, rgba(6, 182, 212, 0.24), transparent 60%),
|
||||
#eef2ff;
|
||||
}
|
||||
|
||||
.login-page.is-dark {
|
||||
--login-card-bg: rgba(15, 23, 42, 0.86);
|
||||
--login-border: rgba(148, 163, 184, 0.16);
|
||||
--login-text: #e2e8f0;
|
||||
--login-muted: #94a3b8;
|
||||
--login-step-bg: rgba(148, 163, 184, 0.06);
|
||||
background:
|
||||
radial-gradient(900px 520px at 12% 8%, rgba(79, 70, 229, 0.32), transparent 62%),
|
||||
radial-gradient(820px 520px at 88% 92%, rgba(6, 182, 212, 0.2), transparent 60%),
|
||||
#070b16;
|
||||
}
|
||||
|
||||
.login-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(rgba(148, 163, 184, 0.12) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(148, 163, 184, 0.12) 1px, transparent 1px);
|
||||
background-size: 46px 46px;
|
||||
mask-image: radial-gradient(ellipse at 50% 42%, #000 26%, transparent 74%);
|
||||
-webkit-mask-image: radial-gradient(ellipse at 50% 42%, #000 26%, transparent 74%);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.login-page.is-dark .login-backdrop {
|
||||
background-image:
|
||||
linear-gradient(rgba(148, 163, 184, 0.16) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(148, 163, 184, 0.16) 1px, transparent 1px);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.login-shell {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.05fr) 440px;
|
||||
width: min(1040px, 100%);
|
||||
overflow: hidden;
|
||||
border-radius: 28px;
|
||||
border: 1px solid var(--login-border);
|
||||
background: var(--login-card-bg);
|
||||
box-shadow: 0 40px 90px -40px rgba(15, 23, 42, 0.55);
|
||||
backdrop-filter: blur(18px);
|
||||
-webkit-backdrop-filter: blur(18px);
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 34px;
|
||||
padding: 44px 46px;
|
||||
color: #fff;
|
||||
background:
|
||||
radial-gradient(120% 120% at 0% 0%, rgba(255, 255, 255, 0.3), transparent 56%),
|
||||
linear-gradient(150deg, #4338ca 0%, #2563eb 48%, #06b6d4 100%);
|
||||
}
|
||||
|
||||
.login-brand::before,
|
||||
.login-brand::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-brand::before {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
right: -120px;
|
||||
top: -90px;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
|
||||
.login-brand::after {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
left: -110px;
|
||||
bottom: -120px;
|
||||
background: rgba(7, 11, 22, 0.18);
|
||||
}
|
||||
|
||||
.login-brand-head {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.login-brand-head strong {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.login-brand-head span {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-size: 11.5px;
|
||||
letter-spacing: 1.2px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.66);
|
||||
}
|
||||
|
||||
.login-brand-body {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-brand-body h1 {
|
||||
margin: 0 0 14px;
|
||||
font-size: 34px;
|
||||
line-height: 1.28;
|
||||
letter-spacing: -0.6px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.login-brand-body p {
|
||||
margin: 0 0 30px;
|
||||
max-width: 340px;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
}
|
||||
|
||||
.login-features {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.login-features li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.login-feature-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
flex: none;
|
||||
border-radius: 12px;
|
||||
font-size: 17px;
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.login-features strong {
|
||||
display: block;
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-features em {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
font-style: normal;
|
||||
font-size: 12.5px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.login-brand-foot {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding-top: 18px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.login-version {
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 42px 40px 30px;
|
||||
min-height: 560px;
|
||||
}
|
||||
|
||||
.login-card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.login-card-head h2 {
|
||||
margin: 0;
|
||||
font-size: 23px;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.3px;
|
||||
color: var(--login-text);
|
||||
}
|
||||
|
||||
.login-card-head p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12.5px;
|
||||
color: var(--login-muted);
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
flex: none;
|
||||
border-radius: 14px;
|
||||
filter: drop-shadow(0 10px 18px rgba(37, 99, 235, 0.28));
|
||||
}
|
||||
|
||||
.login-steps {
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
margin: 0 0 20px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.login-steps li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 9px 10px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--login-border);
|
||||
background: var(--login-step-bg);
|
||||
font-size: 12.5px;
|
||||
color: var(--login-muted);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.login-steps li.is-active {
|
||||
color: #38bdf8;
|
||||
border-color: rgba(56, 189, 248, 0.45);
|
||||
background: rgba(56, 189, 248, 0.12);
|
||||
}
|
||||
|
||||
.login-step-icon {
|
||||
display: inline-flex;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-form .ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.login-form .ant-form-item-label {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.login-form .ant-form-item-label > label {
|
||||
font-size: 12.5px;
|
||||
color: var(--login-muted);
|
||||
}
|
||||
|
||||
.login-form .ant-input-affix-wrapper,
|
||||
.login-form .ant-input {
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
.login-form .ant-input-affix-wrapper .ant-input {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.login-code-btn {
|
||||
height: auto !important;
|
||||
padding: 0 2px !important;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.login-submit-item {
|
||||
margin-top: 22px;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.login-submit-item .ant-btn {
|
||||
height: 46px;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.login-submit-item .ant-btn-primary {
|
||||
border: none;
|
||||
background: linear-gradient(135deg, #4f46e5 0%, #2563eb 55%, #06b6d4 100%);
|
||||
box-shadow: 0 16px 30px -16px rgba(37, 99, 235, 0.9);
|
||||
}
|
||||
|
||||
.login-submit-item .ant-btn-primary:not(:disabled):hover {
|
||||
background: linear-gradient(135deg, #4338ca 0%, #1d4ed8 55%, #0891b2 100%);
|
||||
}
|
||||
|
||||
.login-waiting {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
padding: 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(56, 189, 248, 0.35);
|
||||
background: rgba(56, 189, 248, 0.1);
|
||||
}
|
||||
|
||||
.login-waiting-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: none;
|
||||
border-radius: 50%;
|
||||
font-size: 17px;
|
||||
color: #38bdf8;
|
||||
background: rgba(56, 189, 248, 0.16);
|
||||
animation: login-pulse 1.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes login-pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.85;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.08);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.login-waiting-body strong {
|
||||
display: block;
|
||||
font-size: 13.5px;
|
||||
color: var(--login-text);
|
||||
}
|
||||
|
||||
.login-waiting-body p {
|
||||
margin: 4px 0 9px;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--login-muted);
|
||||
}
|
||||
|
||||
.login-copy {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.login-notice {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
color: #b45309;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.login-page.is-dark .login-notice {
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.login-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-top: auto;
|
||||
padding-top: 16px;
|
||||
border-top: 1px dashed var(--login-border);
|
||||
font-size: 12.5px;
|
||||
color: var(--login-muted);
|
||||
}
|
||||
|
||||
.login-switch-center {
|
||||
justify-content: center;
|
||||
border-top: none;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.login-shell {
|
||||
grid-template-columns: 1fr;
|
||||
width: min(462px, 100%);
|
||||
}
|
||||
|
||||
.login-brand {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
min-height: 0;
|
||||
padding: 34px 26px 26px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.login-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.login-steps li {
|
||||
padding: 8px 6px;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.login-card-head h2 {
|
||||
font-size: 21px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Alert, Button, Card, Divider, Form, Input, Space, Typography } from 'antd';
|
||||
import { Button, Form, Input, Space, Typography } from 'antd';
|
||||
import type { InputRef } from 'antd';
|
||||
import { message } from '../utils/antdMessage';
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
DesktopOutlined,
|
||||
LoadingOutlined,
|
||||
LockOutlined,
|
||||
MobileOutlined,
|
||||
ReloadOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
SafetyOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@@ -16,11 +21,50 @@ import { getErrorMessage } from '../utils/error';
|
||||
import { useTheme } from '../store/useTheme';
|
||||
import { useAppInfo } from '../hooks/useAppInfo';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Text } = Typography;
|
||||
|
||||
type LoginMode = 'secure' | 'legacy';
|
||||
type LoginValues = { username: string; password: string; smsCode?: string };
|
||||
|
||||
const FLOW_STEPS = [
|
||||
{ title: '账号密码', icon: <LockOutlined /> },
|
||||
{ title: '短信验证码', icon: <MobileOutlined /> },
|
||||
{ title: '本机确认', icon: <SafetyCertificateOutlined /> },
|
||||
];
|
||||
|
||||
const BRAND_FEATURES = [
|
||||
{ icon: <LockOutlined />, title: '账号密码', desc: '第一重校验,拒绝弱口令' },
|
||||
{ icon: <MobileOutlined />, title: '短信验证码', desc: '绑定手机接收一次性验证码' },
|
||||
{ icon: <DesktopOutlined />, title: '本机 DCAuth', desc: 'Ed25519 设备签名,换机即失效' },
|
||||
];
|
||||
|
||||
function LogoMark({ size = 40 }: { size?: number }) {
|
||||
return (
|
||||
<svg
|
||||
className="login-logo"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 64 64"
|
||||
role="img"
|
||||
aria-label="直播运营后台"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="loginBrand" x1="4" y1="2" x2="60" y2="62" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stopColor="#6366F1" />
|
||||
<stop offset="0.55" stopColor="#3B82F6" />
|
||||
<stop offset="1" stopColor="#06B6D4" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="64" height="64" rx="17" fill="url(#loginBrand)" />
|
||||
<path
|
||||
d="M25.6 20.4c0-1.15 1.26-1.86 2.24-1.26l18.2 11.6c.94.6.94 1.98 0 2.58l-18.2 11.6c-.98.6-2.24-.11-2.24-1.26V20.4Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
<circle cx="47.5" cy="16.5" r="6.2" fill="#F43F5E" stroke="#0B1020" strokeWidth="2.6" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function errorCodeOf(error: unknown): string {
|
||||
return (error as { errorCode?: string } | null)?.errorCode || '';
|
||||
}
|
||||
@@ -228,161 +272,235 @@ export default function LoginPage({ onLogin }: { onLogin?: () => void }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
background: isDark
|
||||
? 'linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)'
|
||||
: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
width: 420,
|
||||
boxShadow: isDark ? '0 8px 24px rgba(0,0,0,0.4)' : '0 8px 24px rgba(0,0,0,0.15)',
|
||||
}}
|
||||
>
|
||||
<Title level={3} style={{ textAlign: 'center', marginBottom: 32 }}>
|
||||
直播运营后台
|
||||
</Title>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
marginTop: -20,
|
||||
marginBottom: 24,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
当前版本 {versionText}
|
||||
</Text>
|
||||
<div className={`login-page${isDark ? ' is-dark' : ''}`}>
|
||||
<div className="login-backdrop" aria-hidden="true" />
|
||||
<div className="login-shell">
|
||||
<aside className="login-brand">
|
||||
<div className="login-brand-head">
|
||||
<LogoMark size={44} />
|
||||
<div>
|
||||
<strong>直播运营后台</strong>
|
||||
<span>Live Operations Console</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="login-brand-body">
|
||||
<h1>
|
||||
安全登录,
|
||||
<br />
|
||||
从这台设备开始
|
||||
</h1>
|
||||
<p>账号密码、短信验证码与本机 DCAuth 签名三重校验,账号被盗也进不来。</p>
|
||||
|
||||
<ul className="login-features">
|
||||
{BRAND_FEATURES.map((item) => (
|
||||
<li key={item.title}>
|
||||
<span className="login-feature-icon">{item.icon}</span>
|
||||
<span>
|
||||
<strong>{item.title}</strong>
|
||||
<em>{item.desc}</em>
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="login-brand-foot">
|
||||
<span className="login-version">{versionText}</span>
|
||||
<span>仅限内部运营人员使用</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section className="login-card">
|
||||
<header className="login-card-head">
|
||||
<LogoMark size={46} />
|
||||
<div>
|
||||
<h2>{mode === 'secure' ? '安全登录' : '密码登录'}</h2>
|
||||
<p>
|
||||
直播运营后台 · <span>{versionText}</span>
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{mode === 'secure' ? (
|
||||
<>
|
||||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message="安全登录"
|
||||
description="用户名密码验证后,还需短信验证码和本机 DCAuth 设备签名。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<ol className="login-steps">
|
||||
{FLOW_STEPS.map((step, index) => (
|
||||
<li
|
||||
key={step.title}
|
||||
className={waiting && index === 2 ? 'is-active' : undefined}
|
||||
>
|
||||
<span className="login-step-icon">{step.icon}</span>
|
||||
<span>{step.title}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
|
||||
{waiting ? (
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="等待本机确认"
|
||||
description={
|
||||
<Space direction="vertical" size={4} style={{ width: '100%' }}>
|
||||
<span>请在弹出的 DCAuth 窗口中点击确认。若未弹出,请先安装并运行一次 DCAuth。</span>
|
||||
{launchUrl ? (
|
||||
<Text code copyable={{ text: launchUrl }} style={{ wordBreak: 'break-all' }}>
|
||||
{launchUrl}
|
||||
<div className="login-waiting">
|
||||
<span className="login-waiting-icon">
|
||||
<LoadingOutlined />
|
||||
</span>
|
||||
<div className="login-waiting-body">
|
||||
<strong>等待本机确认</strong>
|
||||
<p>请在 DCAuth 弹窗中点击「允许登录」;没弹出就点下面按钮重新唤起。</p>
|
||||
<Space size={8} wrap>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={() => launchProtocol(launchUrl)}
|
||||
>
|
||||
重新唤起 DCAuth
|
||||
</Button>
|
||||
<Text code copyable={{ text: launchUrl }} className="login-copy">
|
||||
复制协议链接
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Form form={form} onFinish={handleSecureLogin} size="large" requiredMark={false}>
|
||||
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
className="login-form"
|
||||
onFinish={handleSecureLogin}
|
||||
size="large"
|
||||
requiredMark={false}
|
||||
>
|
||||
<Form.Item
|
||||
label="账号"
|
||||
name="username"
|
||||
rules={[{ required: true, message: '请输入用户名' }]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder="用户名(中文 / 英文 / 数字)"
|
||||
placeholder="中文 / 英文 / 数字账号"
|
||||
autoComplete="username"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
placeholder="密码"
|
||||
autoComplete="current-password"
|
||||
ref={passwordRef}
|
||||
disabled={waiting}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="密码"
|
||||
name="password"
|
||||
rules={[{ required: true, message: '请输入密码' }]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
placeholder="请输入登录密码"
|
||||
autoComplete="current-password"
|
||||
ref={passwordRef}
|
||||
disabled={waiting}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="短信验证码"
|
||||
name="smsCode"
|
||||
rules={[{ required: true, message: '请输入短信验证码' }]}
|
||||
>
|
||||
<Input
|
||||
prefix={<SafetyCertificateOutlined />}
|
||||
placeholder={phoneMasked ? `6 位验证码(${phoneMasked})` : '6 位短信验证码'}
|
||||
prefix={<SafetyOutlined />}
|
||||
placeholder={phoneMasked ? `已发送至 ${phoneMasked}` : '6 位数字验证码'}
|
||||
maxLength={6}
|
||||
inputMode="numeric"
|
||||
autoComplete="one-time-code"
|
||||
disabled={waiting}
|
||||
addonAfter={
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
htmlType="button"
|
||||
loading={sendingCode}
|
||||
disabled={countdown > 0 || loading}
|
||||
disabled={countdown > 0 || loading || waiting}
|
||||
onClick={() => void sendCode()}
|
||||
style={{ padding: 0, height: 'auto' }}
|
||||
className="login-code-btn"
|
||||
>
|
||||
{countdown > 0 ? `${countdown}s 后重发` : '获取验证码'}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item className="login-submit-item">
|
||||
<Button type="primary" htmlType="submit" loading={loading} block>
|
||||
安全登录 · 拉起本机验证
|
||||
{waiting ? '等待本机确认…' : '安全登录 · 验证本机'}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Divider plain style={{ margin: '4px 0 12px' }}>
|
||||
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||
未绑定手机号的旧账号
|
||||
</Text>
|
||||
</Divider>
|
||||
<Button type="link" block onClick={() => switchMode('legacy')} disabled={loading}>
|
||||
旧账号仅密码登录
|
||||
<div className="login-switch">
|
||||
<span>未绑定手机号的旧账号</span>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => switchMode('legacy')}
|
||||
disabled={loading || waiting}
|
||||
>
|
||||
仅密码登录
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Alert
|
||||
type="warning"
|
||||
showIcon
|
||||
message="旧账号仅密码登录"
|
||||
description="仅未绑定手机号的账号可用;已绑定手机号的账号会被要求使用安全登录。"
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<Form form={form} onFinish={handleLegacyLogin} size="large" requiredMark={false}>
|
||||
<Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}>
|
||||
<div className="login-notice">
|
||||
<SafetyCertificateOutlined />
|
||||
<span>
|
||||
仅未绑定手机号的账号可用;已绑定手机号的账号会被要求使用安全登录。
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
className="login-form"
|
||||
onFinish={handleLegacyLogin}
|
||||
size="large"
|
||||
requiredMark={false}
|
||||
>
|
||||
<Form.Item
|
||||
label="账号"
|
||||
name="username"
|
||||
rules={[{ required: true, message: '请输入用户名' }]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder="用户名"
|
||||
placeholder="中文 / 英文 / 数字账号"
|
||||
autoComplete="username"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}>
|
||||
<Form.Item
|
||||
label="密码"
|
||||
name="password"
|
||||
rules={[{ required: true, message: '请输入密码' }]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
placeholder="密码"
|
||||
placeholder="请输入登录密码"
|
||||
autoComplete="current-password"
|
||||
ref={passwordRef}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Form.Item className="login-submit-item">
|
||||
<Button type="primary" htmlType="submit" loading={loading} block>
|
||||
密码登录
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Button type="link" block onClick={() => switchMode('secure')} disabled={loading}>
|
||||
<MobileOutlined /> 返回安全登录
|
||||
|
||||
<div className="login-switch login-switch-center">
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={() => switchMode('secure')}
|
||||
disabled={loading}
|
||||
>
|
||||
返回安全登录
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user