You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/ui/page/login.html

666 lines
19 KiB
HTML

11 months ago
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
3 weeks ago
<meta name="description" content="登录与注册页面" details="提供用户登录和注册功能的页面,支持用户名/手机号登录及第三方登录">
11 months ago
<title>登录与注册</title>
<style>
body {
3 weeks ago
font-family: var(--font-family);
11 months ago
height: 100vh;
overflow: hidden;
3 weeks ago
background-color: var(--bg-color-secondary);
11 months ago
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
3 weeks ago
/* 背景装饰 */
.bg-decoration {
11 months ago
position: absolute;
top: 0;
left: 0;
3 weeks ago
width: 100%;
height: 100%;
z-index: 0;
11 months ago
overflow: hidden;
}
3 weeks ago
.circle {
11 months ago
position: absolute;
border-radius: 50%;
3 weeks ago
background: radial-gradient(circle, color-mix(in srgb, var(--color-primary), transparent 70%), transparent);
filter: blur(40px);
animation: float 10s infinite ease-in-out;
11 months ago
}
3 weeks ago
.circle:nth-child(1) {
width: 500px;
height: 500px;
top: -100px;
left: -100px;
animation-delay: 0s;
11 months ago
}
3 weeks ago
.circle:nth-child(2) {
width: 400px;
height: 400px;
bottom: -50px;
right: -50px;
background: radial-gradient(circle, color-mix(in srgb, var(--color-secondary), transparent 70%), transparent);
animation-delay: -5s;
11 months ago
}
3 weeks ago
@keyframes float {
3 weeks ago
0%,
100% {
transform: translate(0, 0);
}
50% {
transform: translate(30px, 20px);
}
11 months ago
}
3 weeks ago
/* 主容器 */
.container {
3 weeks ago
background-color: var(--color-primary-text);
3 weeks ago
border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl);
position: relative;
overflow: hidden;
width: 850px;
max-width: 95%;
min-height: 600px;
z-index: 1;
11 months ago
}
3 weeks ago
.form-container {
position: absolute;
top: 0;
height: 100%;
transition: all 0.6s ease-in-out;
3 weeks ago
background: var(--color-primary-text);
11 months ago
}
3 weeks ago
.sign-in-container {
left: 0;
width: 50%;
z-index: 2;
11 months ago
}
3 weeks ago
.sign-up-container {
left: 0;
width: 50%;
opacity: 0;
z-index: 1;
11 months ago
}
3 weeks ago
.container.right-panel-active .sign-in-container {
transform: translateX(100%);
11 months ago
}
3 weeks ago
.container.right-panel-active .sign-up-container {
transform: translateX(100%);
opacity: 1;
z-index: 5;
animation: show 0.6s;
11 months ago
}
3 weeks ago
@keyframes show {
3 weeks ago
0%,
49.99% {
opacity: 0;
z-index: 1;
}
50%,
100% {
opacity: 1;
z-index: 5;
}
6 months ago
}
3 weeks ago
.form-container-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 0 40px;
text-align: center;
6 months ago
}
3 weeks ago
/* 标题与文本 */
h1 {
font-weight: bold;
margin: 0 0 10px;
color: var(--text-color);
font-size: 28px;
}
6 months ago
3 weeks ago
.subtitle {
font-size: 14px;
color: var(--text-color-secondary);
margin-bottom: 20px;
6 months ago
}
3 weeks ago
.social-container {
margin: 15px 0 20px;
display: flex;
gap: 15px;
6 months ago
}
3 weeks ago
.social-btn {
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid var(--border-color);
11 months ago
display: flex;
align-items: center;
justify-content: center;
3 weeks ago
color: var(--text-color-secondary);
transition: all 0.3s;
cursor: pointer;
}
3 weeks ago
3 weeks ago
.social-btn:hover {
border-color: var(--color-primary);
color: var(--color-primary);
background-color: color-mix(in srgb, var(--color-primary), transparent 95%);
11 months ago
}
3 weeks ago
/* 切换 Tab */
.login-tab {
display: flex;
margin-bottom: 25px;
position: relative;
background: var(--bg-color-secondary);
border-radius: var(--radius-full);
padding: 4px;
11 months ago
width: 100%;
}
3 weeks ago
.tab-item {
flex: 1;
text-align: center;
padding: 8px 0;
font-size: 14px;
cursor: pointer;
border-radius: var(--radius-full);
color: var(--text-color-secondary);
transition: all 0.3s ease;
11 months ago
position: relative;
3 weeks ago
z-index: 1;
11 months ago
}
3 weeks ago
.tab-item.active {
color: var(--color-primary);
3 weeks ago
background: var(--color-primary-text);
3 weeks ago
box-shadow: var(--shadow-sm);
font-weight: 600;
11 months ago
}
3 weeks ago
/* 输入框区域 */
.input-group {
width: 100%;
display: flex;
flex-direction: column;
gap: 15px;
margin-bottom: 10px;
11 months ago
}
3 weeks ago
.phone-row {
display: flex;
gap: 10px;
}
3 weeks ago
3 weeks ago
.verify-row {
display: flex;
gap: 10px;
11 months ago
}
3 weeks ago
/* 链接与错误信息 */
.forgot-password {
color: var(--text-color-tertiary);
font-size: 13px;
text-decoration: none;
margin: 15px 0;
align-self: flex-end;
transition: color 0.3s;
}
3 weeks ago
3 weeks ago
.forgot-password:hover {
color: var(--color-primary);
11 months ago
}
3 weeks ago
.error-message {
color: var(--color-danger);
font-size: 13px;
min-height: 20px;
margin-bottom: 10px;
text-align: left;
width: 100%;
}
/* 侧边遮罩 */
11 months ago
.overlay-container {
position: absolute;
top: 0;
left: 50%;
width: 50%;
height: 100%;
overflow: hidden;
transition: transform 0.6s ease-in-out;
z-index: 100;
3 weeks ago
border-top-right-radius: var(--radius-xl);
border-bottom-right-radius: var(--radius-xl);
11 months ago
}
.container.right-panel-active .overlay-container {
transform: translateX(-100%);
3 weeks ago
border-radius: var(--radius-xl) 0 0 var(--radius-xl);
11 months ago
}
.overlay {
3 weeks ago
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
11 months ago
background-repeat: no-repeat;
background-size: cover;
background-position: 0 0;
3 weeks ago
color: var(--color-primary-text);
11 months ago
position: relative;
left: -100%;
height: 100%;
width: 200%;
transform: translateX(0);
transition: transform 0.6s ease-in-out;
}
.container.right-panel-active .overlay {
transform: translateX(50%);
}
.overlay-panel {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: 0 40px;
text-align: center;
top: 0;
height: 100%;
width: 50%;
transform: translateX(0);
transition: transform 0.6s ease-in-out;
}
3 weeks ago
.overlay-panel h1 {
3 weeks ago
color: var(--color-primary-text);
3 weeks ago
}
.overlay-panel p {
font-size: 14px;
font-weight: 300;
line-height: 24px;
margin: 20px 0 30px;
3 weeks ago
color: color-mix(in srgb, var(--color-primary-text), transparent 10%);
3 weeks ago
}
11 months ago
.overlay-left {
transform: translateX(-20%);
}
.container.right-panel-active .overlay-left {
transform: translateX(0);
}
.overlay-right {
right: 0;
transform: translateX(0);
}
.container.right-panel-active .overlay-right {
transform: translateX(20%);
}
3 weeks ago
/* 按钮覆盖样式 - REMOVED to avoid conflict with internal styles */
6 months ago
3 weeks ago
/* v-btn 通用样式微调 - REMOVED to avoid conflict with internal styles */
11 months ago
</style>
</head>
<body layout='public'>
3 weeks ago
<!-- 背景动画 -->
<div class="bg-decoration">
<div class="circle"></div>
<div class="circle"></div>
11 months ago
</div>
<div class="container" :class="{ 'right-panel-active': isSignUp }" id="container">
3 weeks ago
6 months ago
<!-- 注册表单 -->
11 months ago
<div class="form-container sign-up-container">
3 weeks ago
<div class="form-container-inner">
11 months ago
<h1>创建账户</h1>
3 weeks ago
<div class="subtitle">填写以下信息开始您的旅程</div>
3 weeks ago
11 months ago
<div class="social-container">
3 weeks ago
<div class="social-btn" @click="handleSocialLogin('github')"><i class="fa-brands fa-github"></i></div>
<div class="social-btn" @click="handleSocialLogin('weixin')"><i class="fa-brands fa-weixin"></i></div>
<div class="social-btn" @click="handleSocialLogin('google')"><i class="fa-brands fa-google"></i></div>
6 months ago
</div>
3 weeks ago
3 weeks ago
<div class="subtitle" style="margin: 0 0 15px; font-size: 12px;">或使用手机/邮箱注册</div>
<div class="input-group">
3 weeks ago
<v-input v:value="signUpForm.username" placeholder="用户名"></v-input>
3 weeks ago
<!-- 手机号输入框带区域选择 -->
<div v-if='$G.cfg.sms' class="phone-row">
3 weeks ago
<v-input type="select" v:value="signUpForm.region" :opts="{options: regions}"></v-input>
<v-input v:value="signUpForm.phone" placeholder="手机号"></v-input>
3 weeks ago
</div>
6 months ago
3 weeks ago
<div v-if='$G.cfg.sms' class="verify-row">
3 weeks ago
<v-input v:value="signUpForm.verifyCode" placeholder="验证码"></v-input>
<v-btn variant="outline" :disabled="smsCountdown > 0 || smsLoading" :click="() => sendVerifyCode('signup')"
style="min-width: 100px;">
3 weeks ago
{{ smsCountdown > 0 ? `${smsCountdown}s` : '获取验证码' }}
</v-btn>
</div>
3 weeks ago
<v-input type="password" v:value="signUpForm.password" placeholder="密码"></v-input>
11 months ago
</div>
3 weeks ago
<div class="error-message">{{ signUpError }}</div>
3 weeks ago
<v-btn round block size="lg" :loading="signUpLoading" :click="handleSignUp">立即注册</v-btn>
3 weeks ago
</div>
11 months ago
</div>
6 months ago
<!-- 登录表单 -->
11 months ago
<div class="form-container sign-in-container">
3 weeks ago
<div class="form-container-inner">
<h1>欢迎回来</h1>
<div class="subtitle">登录您的账户以继续</div>
11 months ago
<div class="social-container">
3 weeks ago
<div class="social-btn" @click="handleSocialLogin('github')"><i class="fa-brands fa-github"></i></div>
<div class="social-btn" @click="handleSocialLogin('weixin')"><i class="fa-brands fa-weixin"></i></div>
<div class="social-btn" @click="handleSocialLogin('google')"><i class="fa-brands fa-google"></i></div>
11 months ago
</div>
3 weeks ago
3 weeks ago
<div class="subtitle" style="margin: 0 0 15px; font-size: 12px;">或使用您的账户</div>
6 months ago
<!-- 登录方式选择 -->
3 weeks ago
<div class="login-tab" v-if="$G.cfg.sms">
6 months ago
<div class="tab-item" :class="{ active: loginType === 'username' }" @click="switchLoginType('username')">
3 weeks ago
账号密码
6 months ago
</div>
<div class="tab-item" :class="{ active: loginType === 'phone' }" @click="switchLoginType('phone')">
3 weeks ago
手机验证码
6 months ago
</div>
</div>
<!-- 用户名登录 -->
3 weeks ago
<div v-if="loginType === 'username'" class="input-group">
3 weeks ago
<v-input v:value="signInForm.username" placeholder="用户名"></v-input>
<v-input type="password" v:value="signInForm.password" placeholder="密码"></v-input>
6 months ago
</div>
<!-- 手机号登录 -->
3 weeks ago
<div v-if="loginType === 'phone'" class="input-group">
<div class="phone-row">
3 weeks ago
<v-input type="select" v:value="signInForm.region" :opts="{options: regions}"></v-input>
<v-input v:value="signInForm.phone" placeholder="手机号"></v-input>
6 months ago
</div>
3 weeks ago
<div class="verify-row">
3 weeks ago
<v-input v:value="signInForm.verifyCode" placeholder="验证码"></v-input>
<v-btn variant="outline" :disabled="smsCountdown > 0 || smsLoading" :click="() => sendVerifyCode('signin')"
style="min-width: 100px;">
3 weeks ago
{{ smsCountdown > 0 ? `${smsCountdown}s` : '获取验证码' }}
</v-btn>
6 months ago
</div>
</div>
3 weeks ago
<a href="#" class="forgot-password">忘记密码?</a>
6 months ago
<div class="error-message">{{ signInError }}</div>
3 weeks ago
<v-btn round block size="lg" :loading="signInLoading" :click="handleSignIn">登 录</v-btn>
3 weeks ago
</div>
11 months ago
</div>
6 months ago
3 weeks ago
<!-- 覆盖层 -->
11 months ago
<div class="overlay-container">
<div class="overlay">
<div class="overlay-panel overlay-left">
3 weeks ago
<h1>已有账户?</h1>
11 months ago
<p>请使用您的个人信息登录,保持连接。</p>
3 weeks ago
<v-btn variant="outline" round class="btn-ghost" :click="switchToSignIn" size="lg"
style="width: 120px;">去登录</v-btn>
11 months ago
</div>
<div class="overlay-panel overlay-right">
3 weeks ago
<h1>新朋友?</h1>
11 months ago
<p>输入您的个人信息,开始您的旅程。</p>
3 weeks ago
<v-btn variant="outline" round class="btn-ghost" :click="switchToSignUp" size="lg"
style="width: 120px;">去注册</v-btn>
11 months ago
</div>
</div>
</div>
</div>
<script setup>
// 响应式数据
6 months ago
logout = $router.query.logout;
redirect = $router.query.redirect || '/';
11 months ago
isSignUp = false;
6 months ago
loginType = 'username'; // 'username' 或 'phone'
signUpForm = {username: '', phone: '', verifyCode: '', password: '', region: '+86'};
signInForm = {username: '', password: '', phone: '', verifyCode: '', region: '+86'};
signUpError = '';
6 months ago
signInError = '';
smsCountdown = 0; // 验证码倒计时
smsLoading = false; // 验证码发送加载状态
signUpLoading = false; // 注册按钮加载状态
signInLoading = false; // 登录按钮加载状态
3 weeks ago
// 常用国家/地区代码 - 转换为 v-select 格式
6 months ago
regions = [
3 weeks ago
{value: '+86', label: '+86 中国'},
{value: '+1', label: '+1 美国'},
{value: '+44', label: '+44 英国'},
{value: '+81', label: '+81 日本'},
{value: '+82', label: '+82 韩国'},
{value: '+65', label: '+65 新加坡'},
{value: '+852', label: '+852 香港'},
{value: '+853', label: '+853 澳门'},
{value: '+886', label: '+886 台湾'},
{value: '+91', label: '+91 印度'},
{value: '+33', label: '+33 法国'},
{value: '+49', label: '+49 德国'},
{value: '+7', label: '+7 俄国'},
{value: '+61', label: '+61 澳大利亚'},
{value: '+55', label: '+55 巴西'},
{value: '+39', label: '+39 意大利'},
{value: '+34', label: '+34 西班牙'},
{value: '+31', label: '+31 荷兰'},
{value: '+46', label: '+46 瑞典'},
{value: '+47', label: '+47 挪威'}
6 months ago
];
// 验证手机号格式(根据不同地区调整)
validatePhone = (phone, region) => {
if (region === '+86') {
const regex = /^1[3-9]\d{9}$/;
return regex.test(phone);
} else if (region === '+1') {
const regex = /^\d{10}$/;
return regex.test(phone);
} else {
const regex = /^\d{7,15}$/;
return regex.test(phone);
}
};
// 验证密码是否符合要求
6 months ago
validatePassword = (password) => {
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[_]).{9,}$/;
return regex.test(password);
};
11 months ago
6 months ago
// 切换登录方式
switchLoginType = (type) => {
3 weeks ago
if (signInLoading) return;
6 months ago
loginType = type;
signInError = '';
signInForm.username = '';
signInForm.password = '';
signInForm.phone = '';
signInForm.verifyCode = '';
};
11 months ago
// 切换到注册页面
switchToSignUp = () => {
3 weeks ago
if (signInLoading || signUpLoading) return;
11 months ago
isSignUp = true;
6 months ago
signUpError = '';
signInError = '';
11 months ago
};
// 切换到登录页面
switchToSignIn = () => {
3 weeks ago
if (signInLoading || signUpLoading) return;
11 months ago
isSignUp = false;
6 months ago
signUpError = '';
signInError = '';
};
// 发送验证码
sendVerifyCode = async (type) => {
3 weeks ago
if (smsLoading) return;
6 months ago
const phone = type === 'signup' ? signUpForm.phone : signInForm.phone;
const region = type === 'signup' ? signUpForm.region : signInForm.region;
if (!phone) {
const errorMsg = '请输入手机号';
3 weeks ago
type === 'signup' ? signUpError = errorMsg : signInError = errorMsg;
6 months ago
return;
}
if (!validatePhone(phone, region)) {
const errorMsg = '请输入正确的手机号格式';
3 weeks ago
type === 'signup' ? signUpError = errorMsg : signInError = errorMsg;
6 months ago
return;
}
3 weeks ago
smsLoading = true;
6 months ago
try {
3 weeks ago
type === 'signup' ? signUpError = '' : signInError = '';
3 weeks ago
await $axios.post('/api/sms', {phone, region, purpose: type});
6 months ago
$message.success('验证码已发送');
smsCountdown = 60;
const timer = setInterval(() => {
smsCountdown--;
3 weeks ago
if (smsCountdown <= 0) clearInterval(timer);
6 months ago
}, 1000);
} catch (error) {
const errorMsg = error.message || '发送验证码失败,请重试';
3 weeks ago
type === 'signup' ? signUpError = errorMsg : signInError = errorMsg;
6 months ago
$message.warning(errorMsg);
} finally {
3 weeks ago
smsLoading = false;
6 months ago
}
11 months ago
};
// 处理第三方登录
handleSocialLogin = (provider) => {
6 months ago
$message.warning(`未开放 ${provider} 登录`);
11 months ago
};
// 处理注册表单提交
3 weeks ago
handleSignUp = async () => {
if (signUpLoading) return;
signUpError = '';
6 months ago
if (signUpForm.username.length < 5) {
6 months ago
signUpError = '用户名必须大于5位。';
return;
}
3 weeks ago
if ($G.cfg.sms) {
if (!validatePhone(signUpForm.phone, signUpForm.region)) {
signUpError = '请输入正确的手机号格式。';
return;
}
if (!signUpForm.verifyCode) {
signUpError = '请输入验证码。';
return;
}
}
6 months ago
if (!validatePassword(signUpForm.password)) {
signUpError = '密码必须大于8位且包含大小写字母、下划线和数字。';
return;
}
3 weeks ago
signUpLoading = true;
11 months ago
try {
3 weeks ago
await $axios.post('/api/user', {
11 months ago
username: signUpForm.username,
6 months ago
phone: signUpForm.phone,
region: signUpForm.region,
verify_code: signUpForm.verifyCode,
code: btoa(signUpForm.password),
}, {noretry: true});
3 weeks ago
6 months ago
$message.success('注册成功!');
signUpForm = {username: '', phone: '', verifyCode: '', password: '', region: '+86'};
switchToSignIn();
11 months ago
} catch (error) {
signUpError = error.message || '注册失败,请重试。';
6 months ago
$message.warning(signUpError);
} finally {
3 weeks ago
signUpLoading = false;
11 months ago
}
};
// 处理登录表单提交
3 weeks ago
handleSignIn = async () => {
if (signInLoading) return;
6 months ago
signInError = '';
11 months ago
try {
6 months ago
let loginData = {};
if (loginType === 'username') {
3 weeks ago
if (!signInForm.username) {signInError = '请输入用户名'; return;}
if (!signInForm.password) {signInError = '请输入密码'; return;}
loginData = {username: signInForm.username, code: btoa(signInForm.password), type: 'username'};
6 months ago
} else {
3 weeks ago
if (!signInForm.phone) {signInError = '请输入手机号'; return;}
if (!signInForm.verifyCode) {signInError = '请输入验证码'; return;}
loginData = {phone: signInForm.phone, region: signInForm.region, verify_code: signInForm.verifyCode, type: 'phone'};
6 months ago
}
3 weeks ago
signInLoading = true;
await $axios.post('/api/token', loginData, {noretry: true});
$message.success('登录成功!');
$router.push(redirect);
11 months ago
} catch (error) {
3 weeks ago
signInError = error.message || '登录失败,请重试';
6 months ago
$message.warning(signInError);
} finally {
3 weeks ago
signInLoading = false;
11 months ago
}
};
</script>
</body>
3 weeks ago
11 months ago
</html>