You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
648 lines
15 KiB
648 lines
15 KiB
<script setup lang="ts">
|
|
const route = useRoute()
|
|
const activeTab = ref<'login' | 'register'>(route.query.tab === 'register' ? 'register' : 'login')
|
|
|
|
const loginForm = reactive({
|
|
email: '',
|
|
password: '',
|
|
})
|
|
|
|
const registerForm = reactive({
|
|
username: '',
|
|
email: '',
|
|
password: '',
|
|
confirmPassword: '',
|
|
})
|
|
|
|
function handleLogin() {
|
|
console.log('Login:', loginForm)
|
|
}
|
|
|
|
function handleRegister() {
|
|
console.log('Register:', registerForm)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="auth-page">
|
|
<!-- Left decorative panel -->
|
|
<div class="auth-panel">
|
|
<div class="panel-content">
|
|
<!-- Floating photo cards -->
|
|
<div class="floating-photos">
|
|
<div class="photo-card photo-card-1">
|
|
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=300&h=200&fit=crop" alt="" />
|
|
</div>
|
|
<div class="photo-card photo-card-2">
|
|
<img src="https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=300&h=200&fit=crop" alt="" />
|
|
</div>
|
|
<div class="photo-card photo-card-3">
|
|
<img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?w=300&h=200&fit=crop" alt="" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel-text">
|
|
<h2>发现美的<br/>收藏世界</h2>
|
|
<p>在这里存储和展示您的收藏,与志同道合的人分享您的视觉故事</p>
|
|
</div>
|
|
|
|
<div class="panel-deco">
|
|
<div class="deco-circle deco-circle-1"></div>
|
|
<div class="deco-circle deco-circle-2"></div>
|
|
<div class="deco-circle deco-circle-3"></div>
|
|
<div class="deco-dots"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right form panel -->
|
|
<div class="auth-form-panel">
|
|
<div class="auth-card">
|
|
<div class="auth-header">
|
|
<h1 class="auth-title">{{ activeTab === 'login' ? '欢迎回来' : '创建账户' }}</h1>
|
|
<p class="auth-subtitle">{{ activeTab === 'login' ? '登录您的账户继续探索' : '加入我们开始探索之旅' }}</p>
|
|
</div>
|
|
|
|
<div class="auth-tabs">
|
|
<button
|
|
class="auth-tab"
|
|
:class="{ active: activeTab === 'login' }"
|
|
@click="activeTab = 'login'"
|
|
>
|
|
登录
|
|
</button>
|
|
<button
|
|
class="auth-tab"
|
|
:class="{ active: activeTab === 'register' }"
|
|
@click="activeTab = 'register'"
|
|
>
|
|
注册
|
|
</button>
|
|
</div>
|
|
|
|
<Transition name="form-fade" mode="out-in">
|
|
<form v-if="activeTab === 'login'" key="login" class="auth-form" @submit.prevent="handleLogin">
|
|
<div class="form-group">
|
|
<label for="login-email">邮箱地址</label>
|
|
<div class="input-wrapper">
|
|
<svg class="input-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
|
<path d="M22 6L12 13 2 6"/>
|
|
</svg>
|
|
<input
|
|
id="login-email"
|
|
v-model="loginForm.email"
|
|
type="email"
|
|
placeholder="your@email.com"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="label-row">
|
|
<label for="login-password">密码</label>
|
|
<a href="#" class="forgot-link">忘记密码?</a>
|
|
</div>
|
|
<div class="input-wrapper">
|
|
<svg class="input-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<rect x="3" y="11" width="18" height="11" rx="2"/>
|
|
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
|
</svg>
|
|
<input
|
|
id="login-password"
|
|
v-model="loginForm.password"
|
|
type="password"
|
|
placeholder="输入密码"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<label class="remember-me">
|
|
<input type="checkbox" />
|
|
<span>记住我</span>
|
|
</label>
|
|
|
|
<button type="submit" class="submit-btn">
|
|
{{ activeTab === 'login' ? '登录' : '创建账户' }}
|
|
</button>
|
|
</form>
|
|
|
|
<form v-else class="auth-form" @submit.prevent="handleRegister">
|
|
<div class="form-group">
|
|
<label for="register-username">用户名</label>
|
|
<div class="input-wrapper">
|
|
<svg class="input-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<circle cx="12" cy="8" r="4"/>
|
|
<path d="M4 20c0-4 4-6 8-6s8 2 8 6"/>
|
|
</svg>
|
|
<input
|
|
id="register-username"
|
|
v-model="registerForm.username"
|
|
type="text"
|
|
placeholder="选择一个用户名"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="register-email">邮箱地址</label>
|
|
<div class="input-wrapper">
|
|
<svg class="input-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<rect x="2" y="4" width="20" height="16" rx="2"/>
|
|
<path d="M22 6L12 13 2 6"/>
|
|
</svg>
|
|
<input
|
|
id="register-email"
|
|
v-model="registerForm.email"
|
|
type="email"
|
|
placeholder="your@email.com"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="register-password">密码</label>
|
|
<div class="input-wrapper">
|
|
<svg class="input-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<rect x="3" y="11" width="18" height="11" rx="2"/>
|
|
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
|
</svg>
|
|
<input
|
|
id="register-password"
|
|
v-model="registerForm.password"
|
|
type="password"
|
|
placeholder="至少 8 位"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="register-confirm">确认密码</label>
|
|
<div class="input-wrapper">
|
|
<svg class="input-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<rect x="3" y="11" width="18" height="11" rx="2"/>
|
|
<path d="M7 11V7a5 5 0 0110 0v4"/>
|
|
<circle cx="12" cy="16" r="1" fill="currentColor"/>
|
|
</svg>
|
|
<input
|
|
id="register-confirm"
|
|
v-model="registerForm.confirmPassword"
|
|
type="password"
|
|
placeholder="再次输入密码"
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="submit-btn">
|
|
创建账户
|
|
</button>
|
|
</form>
|
|
</Transition>
|
|
|
|
<div class="auth-divider">
|
|
<span>或</span>
|
|
</div>
|
|
|
|
<button class="social-btn">
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
|
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
|
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
|
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
|
</svg>
|
|
使用 Google 继续
|
|
</button>
|
|
|
|
<p class="auth-footer">
|
|
<template v-if="activeTab === 'login'">
|
|
还没有账户?<a href="#" @click.prevent="activeTab = 'register'">立即注册</a>
|
|
</template>
|
|
<template v-else>
|
|
已有账户?<a href="#" @click.prevent="activeTab = 'login'">立即登录</a>
|
|
</template>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.auth-page {
|
|
min-height: 100vh;
|
|
display: flex;
|
|
}
|
|
|
|
/* ── Left Panel ── */
|
|
|
|
.auth-panel {
|
|
flex: 1;
|
|
display: none;
|
|
background: var(--color-surface-dark);
|
|
padding: 48px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
@media (min-width: 900px) {
|
|
.auth-panel {
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.panel-content {
|
|
position: relative;
|
|
z-index: 2;
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
}
|
|
|
|
/* ── Floating Photos ── */
|
|
|
|
.floating-photos {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 320px;
|
|
height: 240px;
|
|
}
|
|
|
|
.photo-card {
|
|
position: absolute;
|
|
background: var(--color-surface-dark-elevated);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.photo-card img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
.photo-card-1 {
|
|
width: 180px;
|
|
height: 120px;
|
|
top: 0;
|
|
left: 0;
|
|
transform: rotate(-8deg);
|
|
z-index: 3;
|
|
}
|
|
|
|
.photo-card-2 {
|
|
width: 160px;
|
|
height: 100px;
|
|
top: 20px;
|
|
right: 10px;
|
|
transform: rotate(6deg);
|
|
z-index: 2;
|
|
}
|
|
|
|
.photo-card-3 {
|
|
width: 140px;
|
|
height: 90px;
|
|
bottom: 10px;
|
|
left: 60px;
|
|
transform: rotate(3deg);
|
|
z-index: 1;
|
|
}
|
|
|
|
/* ── Dots Pattern ── */
|
|
|
|
.deco-dots {
|
|
position: absolute;
|
|
inset: 0;
|
|
background-image: radial-gradient(circle, rgba(255,255,255,0.08) 1px, transparent 1px);
|
|
background-size: 24px 24px;
|
|
}
|
|
|
|
.panel-text {
|
|
margin-top: auto;
|
|
margin-bottom: 48px;
|
|
}
|
|
|
|
.panel-text h2 {
|
|
font-family: var(--font-display);
|
|
font-size: 42px;
|
|
font-weight: 400;
|
|
color: var(--color-on-dark);
|
|
letter-spacing: -1px;
|
|
line-height: 1.15;
|
|
margin: 0 0 20px;
|
|
}
|
|
|
|
.panel-text p {
|
|
font-family: var(--font-body);
|
|
font-size: 16px;
|
|
color: var(--color-on-dark-soft);
|
|
line-height: 1.6;
|
|
margin: 0;
|
|
max-width: 360px;
|
|
}
|
|
|
|
.panel-deco {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.deco-circle {
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--color-primary) 0%, transparent 60%);
|
|
opacity: 0.15;
|
|
}
|
|
|
|
.deco-circle-1 {
|
|
width: 400px;
|
|
height: 400px;
|
|
top: -100px;
|
|
right: -100px;
|
|
}
|
|
|
|
.deco-circle-2 {
|
|
width: 300px;
|
|
height: 300px;
|
|
bottom: 20%;
|
|
left: -80px;
|
|
background: linear-gradient(135deg, var(--color-accent-teal) 0%, transparent 60%);
|
|
opacity: 0.1;
|
|
}
|
|
|
|
.deco-circle-3 {
|
|
width: 200px;
|
|
height: 200px;
|
|
bottom: -50px;
|
|
right: 20%;
|
|
opacity: 0.08;
|
|
}
|
|
|
|
/* ── Right Form Panel ── */
|
|
|
|
.auth-form-panel {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 48px 24px;
|
|
background: linear-gradient(135deg, var(--color-canvas) 0%, var(--color-surface-soft) 100%);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.auth-card {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
}
|
|
|
|
/* ── Form Transition ── */
|
|
|
|
.form-fade-enter-active,
|
|
.form-fade-leave-active {
|
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
}
|
|
|
|
.form-fade-enter-from {
|
|
opacity: 0;
|
|
transform: translateX(12px);
|
|
}
|
|
|
|
.form-fade-leave-to {
|
|
opacity: 0;
|
|
transform: translateX(-12px);
|
|
}
|
|
|
|
.auth-header {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.auth-title {
|
|
font-family: var(--font-display);
|
|
font-size: 32px;
|
|
font-weight: 400;
|
|
color: var(--color-ink);
|
|
letter-spacing: -0.5px;
|
|
margin: 0 0 8px;
|
|
}
|
|
|
|
.auth-subtitle {
|
|
font-family: var(--font-body);
|
|
font-size: 15px;
|
|
color: var(--color-muted);
|
|
margin: 0;
|
|
}
|
|
|
|
.auth-tabs {
|
|
display: flex;
|
|
gap: 4px;
|
|
margin-bottom: 32px;
|
|
padding: 4px;
|
|
background: var(--color-surface-card);
|
|
border-radius: 12px;
|
|
}
|
|
|
|
.auth-tab {
|
|
flex: 1;
|
|
padding: 12px 20px;
|
|
font-family: var(--font-body);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--color-muted);
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
transition: all 0.25s ease;
|
|
}
|
|
|
|
.auth-tab.active {
|
|
background: var(--color-canvas);
|
|
color: var(--color-ink);
|
|
box-shadow: 0 2px 8px rgba(20, 20, 19, 0.08);
|
|
}
|
|
|
|
.auth-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.form-group label {
|
|
font-family: var(--font-body);
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--color-body-strong);
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
}
|
|
|
|
.input-icon {
|
|
position: absolute;
|
|
left: 14px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--color-muted-soft);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.form-group input {
|
|
width: 100%;
|
|
padding: 14px 14px 14px 44px;
|
|
font-family: var(--font-body);
|
|
font-size: 15px;
|
|
color: var(--color-ink);
|
|
background: var(--color-canvas);
|
|
border: 1.5px solid var(--color-hairline);
|
|
border-radius: 12px;
|
|
outline: none;
|
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.form-group input::placeholder {
|
|
color: var(--color-muted-soft);
|
|
}
|
|
|
|
.form-group input:focus {
|
|
border-color: var(--color-primary);
|
|
box-shadow: 0 0 0 4px rgba(204, 120, 92, 0.12);
|
|
}
|
|
|
|
.label-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.forgot-link {
|
|
font-size: 13px;
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.forgot-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.remember-me {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
margin-top: -4px;
|
|
}
|
|
|
|
.remember-me input {
|
|
width: 18px;
|
|
height: 18px;
|
|
accent-color: var(--color-primary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.remember-me span {
|
|
font-size: 13px;
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
.submit-btn {
|
|
width: 100%;
|
|
padding: 16px;
|
|
margin-top: 8px;
|
|
font-family: var(--font-body);
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
color: var(--color-on-primary);
|
|
background: var(--color-primary);
|
|
border: none;
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: background 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
|
|
box-shadow: 0 4px 14px rgba(204, 120, 92, 0.25);
|
|
}
|
|
|
|
.submit-btn:hover {
|
|
background: var(--color-primary-active);
|
|
box-shadow: 0 6px 20px rgba(204, 120, 92, 0.35);
|
|
}
|
|
|
|
.submit-btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
.auth-divider {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
margin: 28px 0;
|
|
}
|
|
|
|
.auth-divider::before,
|
|
.auth-divider::after {
|
|
content: '';
|
|
flex: 1;
|
|
height: 1px;
|
|
background: var(--color-hairline);
|
|
}
|
|
|
|
.auth-divider span {
|
|
font-size: 13px;
|
|
color: var(--color-muted-soft);
|
|
}
|
|
|
|
.social-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
padding: 14px 20px;
|
|
font-family: var(--font-body);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--color-ink);
|
|
background: var(--color-canvas);
|
|
border: 1.5px solid var(--color-hairline);
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: background 0.2s ease, border-color 0.2s ease;
|
|
}
|
|
|
|
.social-btn:hover {
|
|
background: var(--color-surface-soft);
|
|
border-color: var(--color-hairline-soft);
|
|
}
|
|
|
|
.auth-footer {
|
|
text-align: center;
|
|
margin-top: 28px;
|
|
font-size: 14px;
|
|
color: var(--color-muted);
|
|
}
|
|
|
|
.auth-footer a {
|
|
color: var(--color-primary);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.auth-footer a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
</style>
|
|
|