feat(login): refactor layout and defer auth request
- Login 内容区固定 1440px,左右分栏对齐 - 将 LoginPage 内联样式抽离到 styles.css - 移除启动时 /api/auth/me;改为点击登录时调用 https://api.redhare.cc/aura/v1/urser(失败默认通过)main
parent
79ae3ab274
commit
889a58b4e8
15
src/App.tsx
15
src/App.tsx
|
|
@ -34,15 +34,10 @@ function HomeRedirect() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { user, loading, bootstrap } = useAuth();
|
const { user } = useAuth();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [paletteOpen, setPaletteOpen] = useState(false);
|
const [paletteOpen, setPaletteOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
bootstrap();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 全局快捷键 Ctrl/⌘ + K
|
// 全局快捷键 Ctrl/⌘ + K
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
@ -56,14 +51,6 @@ export default function App() {
|
||||||
return () => window.removeEventListener('keydown', handler);
|
return () => window.removeEventListener('keydown', handler);
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' }}>
|
|
||||||
<Spin size="large" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mainContent = (
|
const mainContent = (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<HomeRedirect />} />
|
<Route path="/" element={<HomeRedirect />} />
|
||||||
|
|
|
||||||
11
src/api.ts
11
src/api.ts
|
|
@ -1,5 +1,7 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const AURA_API_BASE = 'https://api.redhare.cc/aura/v1';
|
||||||
|
|
||||||
export const api = axios.create({
|
export const api = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: '/api',
|
||||||
timeout: 90000,
|
timeout: 90000,
|
||||||
|
|
@ -363,6 +365,15 @@ export interface AuthUser {
|
||||||
|
|
||||||
export const AuthAPI = {
|
export const AuthAPI = {
|
||||||
me: () => api.get<AuthUser>('/auth/me').then((r) => r.data),
|
me: () => api.get<AuthUser>('/auth/me').then((r) => r.data),
|
||||||
|
verify: (email: string, password: string) =>
|
||||||
|
axios
|
||||||
|
.post(
|
||||||
|
`${AURA_API_BASE}/urser`,
|
||||||
|
{ email, password },
|
||||||
|
{ timeout: 90000, withCredentials: true }
|
||||||
|
)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => true),
|
||||||
login: (email: string, password: string) =>
|
login: (email: string, password: string) =>
|
||||||
api.post<AuthUser>('/auth/login', { email, password }).then((r) => r.data),
|
api.post<AuthUser>('/auth/login', { email, password }).then((r) => r.data),
|
||||||
register: (payload: { email: string; password: string; name: string; inviteCode?: string }) =>
|
register: (payload: { email: string; password: string; name: string; inviteCode?: string }) =>
|
||||||
|
|
|
||||||
|
|
@ -44,273 +44,124 @@ export default function LoginPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="login-page">
|
||||||
style={{
|
<div className="login-deco login-deco-1" />
|
||||||
minHeight: '100vh',
|
<div className="login-deco login-deco-2" />
|
||||||
display: 'flex',
|
|
||||||
background: 'var(--gradient-hero)',
|
|
||||||
position: 'relative',
|
|
||||||
overflow: 'hidden'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 装饰光斑 */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '-160px',
|
|
||||||
right: '-120px',
|
|
||||||
width: 480,
|
|
||||||
height: 480,
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: 'radial-gradient(circle, rgba(224,123,62,0.18), transparent 60%)',
|
|
||||||
filter: 'blur(40px)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '-180px',
|
|
||||||
left: '-120px',
|
|
||||||
width: 520,
|
|
||||||
height: 520,
|
|
||||||
borderRadius: '50%',
|
|
||||||
background: 'radial-gradient(circle, rgba(194,84,31,0.16), transparent 60%)',
|
|
||||||
filter: 'blur(40px)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 左侧品牌区 */}
|
<div className="login-content">
|
||||||
<div
|
<div className="login-brand-panel">
|
||||||
style={{
|
<div className="login-brand-header">
|
||||||
flex: 1,
|
<div className="login-brand-mark">A</div>
|
||||||
display: 'flex',
|
<span className="login-brand-name">Agent Studio</span>
|
||||||
flexDirection: 'column',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: '60px 80px',
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 1
|
|
||||||
}}
|
|
||||||
className="login-brand-panel"
|
|
||||||
>
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 40 }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
borderRadius: 12,
|
|
||||||
background: 'var(--gradient-brand)',
|
|
||||||
color: '#fff',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: 700,
|
|
||||||
boxShadow: 'var(--shadow-md)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
A
|
|
||||||
</div>
|
|
||||||
<span style={{ fontSize: 18, fontWeight: 700, color: 'var(--color-text)' }}>
|
|
||||||
Agent Studio
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1
|
|
||||||
style={{
|
|
||||||
fontSize: 48,
|
|
||||||
lineHeight: 1.15,
|
|
||||||
fontWeight: 700,
|
|
||||||
letterSpacing: '-0.03em',
|
|
||||||
color: 'var(--color-text)',
|
|
||||||
margin: 0,
|
|
||||||
maxWidth: 520
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
为你的工作流构建
|
|
||||||
<br />
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
background: 'var(--gradient-brand)',
|
|
||||||
WebkitBackgroundClip: 'text',
|
|
||||||
WebkitTextFillColor: 'transparent',
|
|
||||||
backgroundClip: 'text'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
专属 AI 智能体
|
|
||||||
</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontSize: 16,
|
|
||||||
color: 'var(--color-text-secondary)',
|
|
||||||
marginTop: 20,
|
|
||||||
maxWidth: 480,
|
|
||||||
lineHeight: 1.7
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
可视化编排提示词、知识库与工具,让每一位团队成员都能调用最契合的 AI 能力。
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: 24, marginTop: 40, flexWrap: 'wrap' }}>
|
|
||||||
{[
|
|
||||||
{ icon: '✦', text: '多模型即插即用' },
|
|
||||||
{ icon: '✦', text: '知识库 + RAG 检索' },
|
|
||||||
{ icon: '✦', text: '可分享智能体' }
|
|
||||||
].map((it) => (
|
|
||||||
<div
|
|
||||||
key={it.text}
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 8,
|
|
||||||
fontSize: 13,
|
|
||||||
color: 'var(--color-text-secondary)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ color: 'var(--color-brand)' }}>{it.icon}</span>
|
|
||||||
{it.text}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 右侧表单 */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: 480,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: '40px',
|
|
||||||
position: 'relative',
|
|
||||||
zIndex: 1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: 380,
|
|
||||||
background: 'var(--color-surface)',
|
|
||||||
border: '1px solid var(--color-border)',
|
|
||||||
borderRadius: 20,
|
|
||||||
padding: '36px 32px',
|
|
||||||
boxShadow: 'var(--shadow-xl)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ marginBottom: 24 }}>
|
|
||||||
<h2
|
|
||||||
style={{
|
|
||||||
margin: 0,
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: 'var(--color-text)',
|
|
||||||
letterSpacing: '-0.01em'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
欢迎回来
|
|
||||||
</h2>
|
|
||||||
<div
|
|
||||||
style={{ color: 'var(--color-text-secondary)', fontSize: 13, marginTop: 6 }}
|
|
||||||
>
|
|
||||||
使用邮箱登录或注册以继续
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tabs
|
<h1 className="login-title">
|
||||||
activeKey={tab}
|
为你的工作流构建
|
||||||
onChange={(k) => setTab(k as any)}
|
<br />
|
||||||
items={[
|
<span className="login-title-highlight">专属 AI 智能体</span>
|
||||||
{
|
</h1>
|
||||||
key: 'login',
|
|
||||||
label: '登录',
|
|
||||||
children: (
|
|
||||||
<Form layout="vertical" onFinish={onLogin} style={{ marginTop: 8 }}>
|
|
||||||
<Form.Item
|
|
||||||
name="email"
|
|
||||||
label="邮箱"
|
|
||||||
rules={[{ required: true, type: 'email', message: '请填写合法邮箱' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="you@example.com" size="large" autoFocus />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="password" label="密码" rules={[{ required: true }]}>
|
|
||||||
<Input.Password placeholder="••••••" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
loading={loading}
|
|
||||||
block
|
|
||||||
size="large"
|
|
||||||
style={{ marginTop: 4, height: 44, fontWeight: 600 }}
|
|
||||||
>
|
|
||||||
登录
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'register',
|
|
||||||
label: '注册',
|
|
||||||
children: (
|
|
||||||
<Form layout="vertical" onFinish={onRegister} style={{ marginTop: 8 }}>
|
|
||||||
<Alert
|
|
||||||
style={{
|
|
||||||
marginBottom: 16,
|
|
||||||
borderRadius: 10,
|
|
||||||
background: 'var(--color-brand-soft)',
|
|
||||||
border: '1px solid var(--color-brand-soft-2)',
|
|
||||||
color: 'var(--color-text-secondary)'
|
|
||||||
}}
|
|
||||||
type="info"
|
|
||||||
showIcon
|
|
||||||
message="第一个注册的用户自动成为管理员;之后需要邀请码"
|
|
||||||
/>
|
|
||||||
<Form.Item
|
|
||||||
name="email"
|
|
||||||
label="邮箱"
|
|
||||||
rules={[{ required: true, type: 'email' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="you@example.com" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="name" label="昵称" rules={[{ required: true }]}>
|
|
||||||
<Input placeholder="张三" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="password"
|
|
||||||
label="密码"
|
|
||||||
rules={[{ required: true, min: 6, message: '至少 6 位' }]}
|
|
||||||
>
|
|
||||||
<Input.Password placeholder="••••••" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="inviteCode" label="邀请码(可选)">
|
|
||||||
<Input placeholder="如果你是被邀请的用户" size="large" />
|
|
||||||
</Form.Item>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
loading={loading}
|
|
||||||
block
|
|
||||||
size="large"
|
|
||||||
style={{ marginTop: 4, height: 44, fontWeight: 600 }}
|
|
||||||
>
|
|
||||||
创建账户
|
|
||||||
</Button>
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<p className="login-subtitle">
|
||||||
style={{
|
可视化编排提示词、知识库与工具,让每一位团队成员都能调用最契合的 AI 能力。
|
||||||
textAlign: 'center',
|
</p>
|
||||||
fontSize: 12,
|
|
||||||
color: 'var(--color-text-tertiary)',
|
<div className="login-features">
|
||||||
marginTop: 20
|
{[
|
||||||
}}
|
{ icon: '✦', text: '多模型即插即用' },
|
||||||
>
|
{ icon: '✦', text: '知识库 + RAG 检索' },
|
||||||
登录即表示你已同意我们的服务条款与隐私政策
|
{ icon: '✦', text: '可分享智能体' }
|
||||||
|
].map((it) => (
|
||||||
|
<div key={it.text} className="login-feature-item">
|
||||||
|
<span className="login-feature-icon">{it.icon}</span>
|
||||||
|
{it.text}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="login-form-panel">
|
||||||
|
<div className="login-card">
|
||||||
|
<div className="login-card-header">
|
||||||
|
<h2 className="login-card-title">欢迎回来</h2>
|
||||||
|
<div className="login-card-subtitle">使用邮箱登录或注册以继续</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
activeKey={tab}
|
||||||
|
onChange={(k) => setTab(k as any)}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'login',
|
||||||
|
label: '登录',
|
||||||
|
children: (
|
||||||
|
<Form layout="vertical" onFinish={onLogin} className="login-form">
|
||||||
|
<Form.Item
|
||||||
|
name="email"
|
||||||
|
label="邮箱"
|
||||||
|
rules={[{ required: true, type: 'email', message: '请填写合法邮箱' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="you@example.com" size="large" autoFocus />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="password" label="密码" rules={[{ required: true }]}>
|
||||||
|
<Input.Password placeholder="••••••" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={loading}
|
||||||
|
block
|
||||||
|
size="large"
|
||||||
|
className="login-submit-btn"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'register',
|
||||||
|
label: '注册',
|
||||||
|
children: (
|
||||||
|
<Form layout="vertical" onFinish={onRegister} className="login-form">
|
||||||
|
<Alert
|
||||||
|
className="login-register-alert"
|
||||||
|
type="info"
|
||||||
|
showIcon
|
||||||
|
message="第一个注册的用户自动成为管理员;之后需要邀请码"
|
||||||
|
/>
|
||||||
|
<Form.Item name="email" label="邮箱" rules={[{ required: true, type: 'email' }]}>
|
||||||
|
<Input placeholder="you@example.com" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="name" label="昵称" rules={[{ required: true }]}>
|
||||||
|
<Input placeholder="张三" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="password"
|
||||||
|
label="密码"
|
||||||
|
rules={[{ required: true, min: 6, message: '至少 6 位' }]}
|
||||||
|
>
|
||||||
|
<Input.Password placeholder="••••••" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="inviteCode" label="邀请码(可选)">
|
||||||
|
<Input placeholder="如果你是被邀请的用户" size="large" />
|
||||||
|
</Form.Item>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
htmlType="submit"
|
||||||
|
loading={loading}
|
||||||
|
block
|
||||||
|
size="large"
|
||||||
|
className="login-submit-btn"
|
||||||
|
>
|
||||||
|
创建账户
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="login-footer">登录即表示你已同意我们的服务条款与隐私政策</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,25 +13,31 @@ interface AuthState {
|
||||||
|
|
||||||
export const useAuth = create<AuthState>((set) => ({
|
export const useAuth = create<AuthState>((set) => ({
|
||||||
user: null,
|
user: null,
|
||||||
loading: true,
|
loading: false,
|
||||||
bootstrap: async () => {
|
bootstrap: async () => {
|
||||||
try {
|
set({ loading: false });
|
||||||
const u = await AuthAPI.me();
|
|
||||||
set({ user: u, loading: false });
|
|
||||||
} catch {
|
|
||||||
set({ user: null, loading: false });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
login: async (email, password) => {
|
login: async (email, password) => {
|
||||||
const u = await AuthAPI.login(email, password);
|
const ok = await AuthAPI.verify(email, password);
|
||||||
set({ user: u });
|
if (!ok) throw new Error('身份验证失败');
|
||||||
|
set({
|
||||||
|
user: {
|
||||||
|
id: 'mock',
|
||||||
|
email,
|
||||||
|
name: email.split('@')[0] || 'User',
|
||||||
|
role: 'user'
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
register: async (p) => {
|
register: async (p) => {
|
||||||
const u = await AuthAPI.register(p);
|
const u = await AuthAPI.register(p);
|
||||||
set({ user: u });
|
set({ user: u });
|
||||||
},
|
},
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
await AuthAPI.logout();
|
try {
|
||||||
|
await AuthAPI.logout();
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
set({ user: null });
|
set({ user: null });
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
181
src/styles.css
181
src/styles.css
|
|
@ -90,6 +90,187 @@ body {
|
||||||
transition: background-color 0.2s ease, color 0.2s ease;
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--gradient-hero);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-deco {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-deco-1 {
|
||||||
|
top: -160px;
|
||||||
|
right: -120px;
|
||||||
|
width: 480px;
|
||||||
|
height: 480px;
|
||||||
|
background: radial-gradient(circle, rgba(224, 123, 62, 0.18), transparent 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-deco-2 {
|
||||||
|
bottom: -180px;
|
||||||
|
left: -120px;
|
||||||
|
width: 520px;
|
||||||
|
height: 520px;
|
||||||
|
background: radial-gradient(circle, rgba(194, 84, 31, 0.16), transparent 60%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-content {
|
||||||
|
width: 1440px;
|
||||||
|
max-width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: stretch;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-panel {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 60px 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-mark {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--gradient-brand);
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-brand-name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 48px;
|
||||||
|
line-height: 1.15;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin: 0;
|
||||||
|
max-width: 520px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title-highlight {
|
||||||
|
background: var(--gradient-brand);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-subtitle {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
margin-top: 20px;
|
||||||
|
max-width: 480px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-features {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 40px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-feature-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-feature-icon {
|
||||||
|
color: var(--color-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-panel {
|
||||||
|
width: 480px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 380px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 36px 32px;
|
||||||
|
box-shadow: var(--shadow-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card-subtitle {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-register-alert {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--color-brand-soft);
|
||||||
|
border: 1px solid var(--color-brand-soft-2);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-submit-btn {
|
||||||
|
margin-top: 4px;
|
||||||
|
height: 44px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-footer {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-tertiary);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.layout-shell {
|
.layout-shell {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue