|
|
<!doctype html>
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
<meta charset="UTF-8" />
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
<title>登录与注册</title>
|
|
|
<meta name="description" content="用户登录与注册页面" />
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
|
|
|
<style>
|
|
|
* {
|
|
|
box-sizing: border-box;
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
font-family: 'Montserrat', sans-serif;
|
|
|
height: 100vh;
|
|
|
overflow: hidden;
|
|
|
background: #ffebee;
|
|
|
/* 红色主题背景 */
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
.background {
|
|
|
position: absolute;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
overflow: hidden;
|
|
|
background: linear-gradient(to right, #ef5350, #e53935);
|
|
|
/* 红色渐变 */
|
|
|
}
|
|
|
|
|
|
.bubble {
|
|
|
position: absolute;
|
|
|
bottom: -150px;
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
border-radius: 50%;
|
|
|
animation: rise linear infinite;
|
|
|
}
|
|
|
|
|
|
@keyframes rise {
|
|
|
0% {
|
|
|
transform: translateY(0) rotate(0);
|
|
|
opacity: 0.5;
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
transform: translateY(-120vh) rotate(360deg);
|
|
|
opacity: 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
h1 {
|
|
|
font-weight: bold;
|
|
|
margin: 0;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
p {
|
|
|
font-size: 14px;
|
|
|
font-weight: 100;
|
|
|
line-height: 20px;
|
|
|
letter-spacing: 0.5px;
|
|
|
margin: 20px 0 30px;
|
|
|
}
|
|
|
|
|
|
span {
|
|
|
font-size: 12px;
|
|
|
margin: 15px 0;
|
|
|
}
|
|
|
|
|
|
a {
|
|
|
color: #c62828;
|
|
|
/* 红色链接 */
|
|
|
font-size: 14px;
|
|
|
text-decoration: none;
|
|
|
margin: 15px 0;
|
|
|
}
|
|
|
|
|
|
button {
|
|
|
border-radius: 20px;
|
|
|
border: 1px solid #e53935;
|
|
|
/* 红色边框 */
|
|
|
background-color: #e53935;
|
|
|
/* 红色背景 */
|
|
|
color: #ffffff;
|
|
|
font-size: 12px;
|
|
|
font-weight: bold;
|
|
|
padding: 12px 45px;
|
|
|
letter-spacing: 1px;
|
|
|
text-transform: uppercase;
|
|
|
transition: transform 80ms ease-in;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
button:active {
|
|
|
transform: scale(0.95);
|
|
|
}
|
|
|
|
|
|
button:focus {
|
|
|
outline: none;
|
|
|
}
|
|
|
|
|
|
button.ghost {
|
|
|
background-color: transparent;
|
|
|
border-color: #ffffff;
|
|
|
}
|
|
|
|
|
|
form {
|
|
|
background-color: #ffffff;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
flex-direction: column;
|
|
|
padding: 0 50px;
|
|
|
height: 100%;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
input {
|
|
|
background-color: #eee;
|
|
|
border: none;
|
|
|
padding: 12px 15px;
|
|
|
margin: 8px 0;
|
|
|
width: 100%;
|
|
|
border-radius: 5px;
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
background-color: #ffffff;
|
|
|
border-radius: 10px;
|
|
|
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
width: 768px;
|
|
|
max-width: 100%;
|
|
|
min-height: 600px;
|
|
|
}
|
|
|
|
|
|
.form-container {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
height: 100%;
|
|
|
transition: all 0.6s ease-in-out;
|
|
|
}
|
|
|
|
|
|
.sign-in-container {
|
|
|
left: 0;
|
|
|
width: 50%;
|
|
|
z-index: 2;
|
|
|
}
|
|
|
|
|
|
.sign-up-container {
|
|
|
left: 0;
|
|
|
width: 50%;
|
|
|
opacity: 0;
|
|
|
z-index: 1;
|
|
|
}
|
|
|
|
|
|
.container.right-panel-active .sign-in-container {
|
|
|
transform: translateX(100%);
|
|
|
}
|
|
|
|
|
|
.container.right-panel-active .sign-up-container {
|
|
|
transform: translateX(100%);
|
|
|
opacity: 1;
|
|
|
z-index: 5;
|
|
|
}
|
|
|
|
|
|
.overlay-container {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
left: 50%;
|
|
|
width: 50%;
|
|
|
height: 100%;
|
|
|
overflow: hidden;
|
|
|
transition: transform 0.6s ease-in-out;
|
|
|
z-index: 100;
|
|
|
}
|
|
|
|
|
|
.container.right-panel-active .overlay-container {
|
|
|
transform: translateX(-100%);
|
|
|
}
|
|
|
|
|
|
.overlay {
|
|
|
background: #e53935;
|
|
|
/* 红色背景 */
|
|
|
background: linear-gradient(to right, #ef5350, #e53935);
|
|
|
/* 红色渐变 */
|
|
|
background-repeat: no-repeat;
|
|
|
background-size: cover;
|
|
|
background-position: 0 0;
|
|
|
color: #ffffff;
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
.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%);
|
|
|
}
|
|
|
|
|
|
.social-container {
|
|
|
margin: 20px 0;
|
|
|
}
|
|
|
|
|
|
.social-container a {
|
|
|
border: 1px solid #dddddd;
|
|
|
border-radius: 50%;
|
|
|
display: inline-flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
margin: 0 5px;
|
|
|
height: 40px;
|
|
|
width: 40px;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.social-container a:hover {
|
|
|
background-color: #f2f2f2;
|
|
|
}
|
|
|
|
|
|
.error-message {
|
|
|
color: red;
|
|
|
font-size: 12px;
|
|
|
margin-top: 8px;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
|
|
|
<body layout='public'>
|
|
|
<div class="background" id="background">
|
|
|
<div v-for="(bubble, index) in bubbles" :key="index" class="bubble" :style="bubble.style"></div>
|
|
|
</div>
|
|
|
|
|
|
<div class="container" :class="{ 'right-panel-active': isSignUp }" id="container">
|
|
|
<div class="form-container sign-up-container">
|
|
|
<form @submit="handleSignUp">
|
|
|
<h1>创建账户</h1>
|
|
|
<div class="social-container">
|
|
|
<a href="#" @click="handleSocialLogin('github')"><i class="fa-brands fa-github"></i></a>
|
|
|
<a href="#" @click="handleSocialLogin('weixin')"><i class="fa-brands fa-weixin"></i></a>
|
|
|
<a href="#" @click="handleSocialLogin('qq')"><i class="fa-brands fa-qq"></i></a>
|
|
|
</div>
|
|
|
<span>或使用您的用户名进行注册</span>
|
|
|
<input type="text" placeholder="用户名" !value="signUpForm.username"
|
|
|
@input="signUpForm.username = $event.target.value" />
|
|
|
<input type="password" placeholder="密码" !value="signUpForm.password"
|
|
|
@input="signUpForm.password = $event.target.value" />
|
|
|
<div class="error-message">{{ signUpError }}</div>
|
|
|
<button>注册</button>
|
|
|
</form>
|
|
|
</div>
|
|
|
<div class="form-container sign-in-container">
|
|
|
<form @submit="handleSignIn">
|
|
|
<h1>登录</h1>
|
|
|
<div class="social-container">
|
|
|
<a href="#" @click="handleSocialLogin('github')"><i class="fa-brands fa-github"></i></a>
|
|
|
<a href="#" @click="handleSocialLogin('weixin')"><i class="fa-brands fa-weixin"></i></a>
|
|
|
<a href="#" @click="handleSocialLogin('qq')"><i class="fa-brands fa-qq"></i></a>
|
|
|
</div>
|
|
|
<span>或使用您的账户</span>
|
|
|
<input type="text" placeholder="用户名" !value="signInForm.username"
|
|
|
@input="signInForm.username = $event.target.value" />
|
|
|
<input type="password" placeholder="密码" !value="signInForm.password"
|
|
|
@input="signInForm.password = $event.target.value" />
|
|
|
<a href="#">忘记密码?</a>
|
|
|
<button>登录</button>
|
|
|
</form>
|
|
|
</div>
|
|
|
<div class="overlay-container">
|
|
|
<div class="overlay">
|
|
|
<div class="overlay-panel overlay-left">
|
|
|
<h1>欢迎回来!</h1>
|
|
|
<p>请使用您的个人信息登录,保持连接。</p>
|
|
|
<button class="ghost" @click="switchToSignIn">登录</button>
|
|
|
</div>
|
|
|
<div class="overlay-panel overlay-right">
|
|
|
<h1>你好,朋友!</h1>
|
|
|
<p>输入您的个人信息,开始您的旅程。</p>
|
|
|
<button class="ghost" @click="switchToSignUp">注册</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script setup>
|
|
|
// 响应式数据
|
|
|
redirect = new URLSearchParams(window.location.search).get('redirect') || '/'
|
|
|
isSignUp = false;
|
|
|
signUpForm = {username: '', password: ''};
|
|
|
signInForm = {username: '', password: ''};
|
|
|
bubbles = [];
|
|
|
signUpError = '';
|
|
|
|
|
|
// 验证密码是否符合要求
|
|
|
const validatePassword = (password) => {
|
|
|
const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[_]).{9,}$/;
|
|
|
return regex.test(password);
|
|
|
};
|
|
|
|
|
|
// 切换到注册页面
|
|
|
switchToSignUp = () => {
|
|
|
isSignUp = true;
|
|
|
};
|
|
|
|
|
|
// 切换到登录页面
|
|
|
switchToSignIn = () => {
|
|
|
isSignUp = false;
|
|
|
};
|
|
|
|
|
|
// 处理第三方登录
|
|
|
handleSocialLogin = (provider) => {
|
|
|
console.log(`尝试使用 ${provider} 登录`);
|
|
|
alert(`已启动 ${provider} 登录!`);
|
|
|
};
|
|
|
|
|
|
function deriveKey(password, salt) {
|
|
|
return CryptoJS.PBKDF2(password, salt, {
|
|
|
keySize: 256 / 32, iterations:
|
|
|
100, hasher: CryptoJS.algo.SHA256
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// 处理注册表单提交
|
|
|
handleSignUp = async (e) => {
|
|
|
e.preventDefault();
|
|
|
signUpError = '';
|
|
|
if (!validatePassword(signUpForm.password) && false) {
|
|
|
signUpError = '密码必须大于8位,且包含大小写字母、下划线和数字。';
|
|
|
return;
|
|
|
}
|
|
|
if (signUpForm.username.length < 2) {
|
|
|
signUpError = '用户名必须大于2位。';
|
|
|
return;
|
|
|
}
|
|
|
signUpError = '';
|
|
|
try {
|
|
|
const response = await api.Post('/api/user', {
|
|
|
username: signUpForm.username,
|
|
|
code: btoa(signUpForm.password),
|
|
|
});
|
|
|
if (response) {
|
|
|
alert('注册成功!');
|
|
|
switchToSignIn();
|
|
|
}
|
|
|
} catch (error) {
|
|
|
signUpError = error.message || '注册失败,请重试。';
|
|
|
console.error(signUpError);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 处理登录表单提交
|
|
|
handleSignIn = async (e) => {
|
|
|
e.preventDefault();
|
|
|
try {
|
|
|
const loginResponse = await api.Post('/api/user/login', {
|
|
|
username: signInForm.username,
|
|
|
code: btoa(signInForm.password),
|
|
|
});
|
|
|
localStorage.setItem('refresh', loginResponse)
|
|
|
if (loginResponse) {
|
|
|
window.location.href = redirect
|
|
|
}
|
|
|
} catch (error) {
|
|
|
alert(error.message || '登录失败,请检查您的凭据。');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 创建动态背景气泡
|
|
|
createBubbles = () => {
|
|
|
const numberOfBubbles = 20;
|
|
|
for (let i = 0; i < numberOfBubbles; i++) {
|
|
|
const size = Math.random() * 100 + 50;
|
|
|
const left = Math.random() * 100;
|
|
|
const delay = Math.random() * 15;
|
|
|
const duration = Math.random() * 15 + 10;
|
|
|
|
|
|
bubbles.push({
|
|
|
style: {
|
|
|
width: `${size}px`,
|
|
|
height: `${size}px`,
|
|
|
left: `${left}%`,
|
|
|
animationDelay: `${delay}s`,
|
|
|
animationDuration: `${duration}s`,
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<script>
|
|
|
// 页面加载时创建气泡
|
|
|
createBubbles();
|
|
|
</script>
|
|
|
</body>
|
|
|
|
|
|
</html>
|