feat: 优化登录页面布局
This commit is contained in:
@@ -23,3 +23,15 @@ body {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.fz-14 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fz-22 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.mt-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@@ -1,363 +1,291 @@
|
||||
<template>
|
||||
<div
|
||||
class="min-h-screen bg-gray-50 flex items-center justify-center p-4"
|
||||
class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4"
|
||||
>
|
||||
<div class="w-full max-w-md">
|
||||
<!-- 登录卡片 -->
|
||||
<div class="bg-white rounded-lg shadow-sm border p-8 w-full max-w-md">
|
||||
<!-- 头部区域 -->
|
||||
<div class="bg-white rounded-lg shadow-xl p-8">
|
||||
<!-- Logo和标题 -->
|
||||
<div class="text-center mb-8">
|
||||
<!-- Logo -->
|
||||
<div class="mb-6">
|
||||
<div class="w-16 h-16 bg-blue-600 rounded-xl flex items-center justify-center mx-auto">
|
||||
<span class="text-white font-bold text-2xl">F</span>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">
|
||||
China Fellou Plus
|
||||
</h1>
|
||||
<div class="text-sm text-gray-600">
|
||||
您的隐私对我们很重要,我们确保您的数据是安全和保密的
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-2">欢迎回来</h1>
|
||||
<p class="text-gray-600">登录您的账户</p>
|
||||
</div>
|
||||
|
||||
<!-- 登录方式切换 -->
|
||||
<div class="flex mb-8 bg-gray-50 rounded-lg p-1">
|
||||
<button
|
||||
@click="loginType = 'code'"
|
||||
:class="[
|
||||
'flex-1 py-2 px-4 rounded-md text-sm font-medium transition-all',
|
||||
loginType === 'code'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-700'
|
||||
]"
|
||||
>
|
||||
验证码登录
|
||||
</button>
|
||||
<button
|
||||
@click="loginType = 'password'"
|
||||
:class="[
|
||||
'flex-1 py-2 px-4 rounded-md text-sm font-medium transition-all',
|
||||
loginType === 'password'
|
||||
? 'bg-white text-blue-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-700'
|
||||
]"
|
||||
>
|
||||
密码登录
|
||||
</button>
|
||||
</div>
|
||||
<div class="login flex items-start justify-center">
|
||||
<!-- 二维码登录 -->
|
||||
<div class="left text-center">
|
||||
<div class="bg-gray-100 rounded-lg p-8 mb-4">
|
||||
<div class="fz-22">手机扫码登录</div>
|
||||
<div
|
||||
class="w-48 h-48 bg-white rounded-lg mx-auto flex items-center justify-center"
|
||||
>
|
||||
<div class="qr text-center mt-20">
|
||||
<vue-qrcode
|
||||
value="https://www.1stg.me"
|
||||
width="195"
|
||||
margin="3"
|
||||
/>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
@submit.prevent="handleLogin"
|
||||
class="space-y-3"
|
||||
>
|
||||
<!-- 邮箱输入框 -->
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
v-model="loginForm.email"
|
||||
type="email"
|
||||
placeholder="请输入邮箱"
|
||||
size="default"
|
||||
class="w-full"
|
||||
:prefix-icon="User"
|
||||
@blur="validateField('email')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 二维码失效 -->
|
||||
<div class="qrcode-error">
|
||||
<p>二维码已失效</p>
|
||||
<el-button type="info" round @click="refreshQrCode"
|
||||
>刷新</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="fz-14">
|
||||
打开<el-text type="success">微信</el-text>扫一扫登录
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 验证码登录 -->
|
||||
<div v-if="loginType === 'code'">
|
||||
<el-form-item prop="verifyCode">
|
||||
<div class="flex gap-2">
|
||||
<!-- 账号密码登录 -->
|
||||
<div class="right text-center">
|
||||
<div class="fz-22">密码登录</div>
|
||||
|
||||
<el-form
|
||||
class="mt-20"
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<!-- 用户名/邮箱/手机号 -->
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.verifyCode"
|
||||
placeholder="请输入验证码"
|
||||
size="default"
|
||||
class="flex-1"
|
||||
:prefix-icon="Timer"
|
||||
maxlength="4"
|
||||
@blur="validateField('verifyCode')"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="default"
|
||||
:disabled="!isEmailValid || countdown > 0"
|
||||
@click="sendVerifyCode"
|
||||
class="min-w-[80px]"
|
||||
v-model="loginForm.username"
|
||||
placeholder="请输入手机号"
|
||||
size="large"
|
||||
clearable
|
||||
>
|
||||
{{ countdown > 0 ? `${countdown}s` : "获取" }}
|
||||
<template #prefix>
|
||||
<el-icon><User /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 密码 -->
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
show-password
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Lock /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 图形验证码 -->
|
||||
<el-form-item v-if="showCaptcha" prop="captcha">
|
||||
<div class="flex space-x-2">
|
||||
<el-input
|
||||
v-model="loginForm.captcha"
|
||||
placeholder="请输入验证码"
|
||||
size="large"
|
||||
clearable
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon><Key /></el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div
|
||||
class="w-24 h-10 bg-gray-200 rounded flex items-center justify-center cursor-pointer"
|
||||
@click="refreshCaptcha"
|
||||
>
|
||||
<span class="text-sm font-mono">{{ captchaCode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 记住我和忘记密码 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<el-checkbox v-model="loginForm.remember"> 记住我 </el-checkbox>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="handleForgotPassword"
|
||||
>
|
||||
忘记密码?
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 密码登录 -->
|
||||
<div v-if="loginType === 'password'">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
placeholder="请输入密码"
|
||||
size="default"
|
||||
class="w-full"
|
||||
:prefix-icon="Lock"
|
||||
@blur="validateField('password')"
|
||||
>
|
||||
<template #suffix>
|
||||
<el-icon
|
||||
@click="showPassword = !showPassword"
|
||||
class="cursor-pointer text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<View v-if="showPassword" />
|
||||
<Hide v-else />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!-- 登录按钮 -->
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
class="w-full"
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
>
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 忘记密码 -->
|
||||
<div class="text-right mb-2">
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="handleForgotPassword"
|
||||
class="text-xs"
|
||||
>
|
||||
忘记密码?
|
||||
</el-link>
|
||||
<!-- 用户协议 -->
|
||||
<div class="text-center fz-14">
|
||||
登录即表示您同意
|
||||
<el-link type="primary" :underline="false">用户协议</el-link>
|
||||
和
|
||||
<el-link type="primary" :underline="false">隐私政策</el-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 协议同意 -->
|
||||
<el-form-item prop="agreed">
|
||||
<el-checkbox
|
||||
v-model="loginForm.agreed"
|
||||
@change="validateField('agreed')"
|
||||
class="text-xs"
|
||||
>
|
||||
<span class="text-gray-600">
|
||||
我已阅读并同意
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="handlePrivacyPolicy"
|
||||
class="text-xs"
|
||||
>隐私政策</el-link
|
||||
>
|
||||
和
|
||||
<el-link
|
||||
type="primary"
|
||||
:underline="false"
|
||||
@click="handleServiceTerms"
|
||||
class="text-xs"
|
||||
>服务条款</el-link
|
||||
>
|
||||
</span>
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="default"
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
class="w-full"
|
||||
>
|
||||
{{ loading ? "登录中..." : "登录" }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Copyright -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { User, Lock, Timer, View, Hide } from "@element-plus/icons-vue";
|
||||
import { User, Lock, Key } from "@element-plus/icons-vue";
|
||||
import VueQrcode from "vue-qrcode";
|
||||
|
||||
// 登录类型
|
||||
const loginType = ref("code");
|
||||
|
||||
// 表单引用
|
||||
// 登录表单
|
||||
const loginFormRef = ref();
|
||||
|
||||
// 表单数据
|
||||
const loginForm = reactive({
|
||||
email: "",
|
||||
username: "",
|
||||
password: "",
|
||||
verifyCode: "",
|
||||
agreed: false,
|
||||
captcha: "",
|
||||
remember: false,
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const loginRules = reactive({
|
||||
email: [
|
||||
{ required: true, message: "请输入邮箱", trigger: "blur" },
|
||||
{ type: "email", message: "请输入正确的邮箱格式", trigger: "blur" },
|
||||
// 登录规则(必填验证)
|
||||
const loginRules = {
|
||||
username: [
|
||||
{ required: true, message: "请输入用户名/邮箱/手机号", trigger: "blur" },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
|
||||
],
|
||||
verifyCode: [
|
||||
{ required: true, message: "请输入验证码", trigger: "blur" },
|
||||
{ len: 4, message: "验证码必须为4位", trigger: "blur" },
|
||||
],
|
||||
agreed: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback(new Error("请同意隐私政策和服务条款"));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: "change",
|
||||
},
|
||||
],
|
||||
});
|
||||
captcha: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
||||
};
|
||||
|
||||
// 状态管理
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
const showPassword = ref(false);
|
||||
const countdown = ref(0);
|
||||
|
||||
// 计算属性
|
||||
const isEmailValid = computed(() => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(loginForm.email);
|
||||
});
|
||||
// 验证码相关
|
||||
const showCaptcha = ref(false);
|
||||
const captchaCode = ref("8K9M");
|
||||
const loginAttempts = ref(0);
|
||||
|
||||
// 验证码倒计时
|
||||
const startCountdown = () => {
|
||||
countdown.value = 60;
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
// 生成二维码
|
||||
const qrCodeUrl = ref("");
|
||||
|
||||
// 发送验证码
|
||||
const sendVerifyCode = async () => {
|
||||
if (!isEmailValid.value) {
|
||||
ElMessage.error("请先输入正确的邮箱地址");
|
||||
return;
|
||||
// 刷新验证码
|
||||
const refreshCaptcha = () => {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let code = "";
|
||||
for (let i = 0; i < 4; i++) {
|
||||
code += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
|
||||
try {
|
||||
// 这里调用发送验证码的API
|
||||
ElMessage.success("验证码已发送到您的邮箱");
|
||||
startCountdown();
|
||||
} catch (error) {
|
||||
ElMessage.error("验证码发送失败,请重试");
|
||||
}
|
||||
};
|
||||
|
||||
// 验证单个字段
|
||||
const validateField = (field) => {
|
||||
loginFormRef.value?.validateField(field);
|
||||
captchaCode.value = code;
|
||||
};
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = async () => {
|
||||
if (!loginFormRef.value) return;
|
||||
|
||||
try {
|
||||
const valid = await loginFormRef.value.validate();
|
||||
if (!valid) return;
|
||||
await loginFormRef.value.validate();
|
||||
|
||||
loading.value = true;
|
||||
|
||||
// 根据登录类型构建请求数据
|
||||
const loginData = {
|
||||
email: loginForm.email,
|
||||
agreed: loginForm.agreed,
|
||||
};
|
||||
|
||||
if (loginType.value === "password") {
|
||||
loginData.password = loginForm.password;
|
||||
} else {
|
||||
loginData.verifyCode = loginForm.verifyCode;
|
||||
}
|
||||
|
||||
// 这里调用登录API
|
||||
console.log("登录数据:", loginData);
|
||||
|
||||
// 模拟登录请求
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
loginAttempts.value++;
|
||||
|
||||
ElMessage.success("登录成功");
|
||||
|
||||
// 登录成功后的跳转逻辑
|
||||
// router.push('/home')
|
||||
// 模拟登录失败,显示验证码
|
||||
if (loginAttempts.value >= 1) {
|
||||
showCaptcha.value = true;
|
||||
ElMessage.error("用户名或密码错误");
|
||||
refreshCaptcha();
|
||||
} else {
|
||||
ElMessage.success("登录成功");
|
||||
}
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
console.error("登录失败:", error);
|
||||
ElMessage.error("登录失败,请重试");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
console.error("验证失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 忘记密码
|
||||
// 处理忘记密码
|
||||
const handleForgotPassword = () => {
|
||||
ElMessage.info("忘记密码功能开发中");
|
||||
ElMessage.info("忘记密码功能开发中...");
|
||||
};
|
||||
|
||||
// 隐私政策
|
||||
const handlePrivacyPolicy = () => {
|
||||
ElMessage.info("隐私政策页面开发中");
|
||||
};
|
||||
|
||||
// 服务条款
|
||||
const handleServiceTerms = () => {
|
||||
ElMessage.info("服务条款页面开发中");
|
||||
};
|
||||
|
||||
// 页面加载时自动聚焦到邮箱输入框
|
||||
// 页面加载时聚焦到用户名输入框
|
||||
onMounted(() => {
|
||||
// 可以在这里添加自动聚焦逻辑
|
||||
const usernameInput = document.querySelector(
|
||||
'input[placeholder="请输入用户名/邮箱/手机号"]'
|
||||
);
|
||||
if (usernameInput) {
|
||||
usernameInput.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Element Plus 样式覆盖 */
|
||||
:deep(.el-input__wrapper) {
|
||||
<style scoped lang="scss">
|
||||
.login {
|
||||
margin: 40px auto;
|
||||
}
|
||||
|
||||
.left {
|
||||
padding-right: 60px;
|
||||
border-right: 1px solid #f2f2f2;
|
||||
}
|
||||
|
||||
.right {
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
.qr {
|
||||
width: 195px;
|
||||
height: 195px;
|
||||
border: 1px solid #e5e8ec;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 0 1px #e5e7eb;
|
||||
transition: all 0.2s;
|
||||
box-shadow: none;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper:hover) {
|
||||
box-shadow: 0 0 0 1px #d1d5db;
|
||||
}
|
||||
.qrcode-error {
|
||||
margin: 14px;
|
||||
width: 167px;
|
||||
height: 167px;
|
||||
background: hsla(0, 0%, 100%, 0.95);
|
||||
font-family: PingFang SC;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1001;
|
||||
|
||||
:deep(.el-input.is-focus .el-input__wrapper) {
|
||||
box-shadow: 0 0 0 2px #3b82f6;
|
||||
}
|
||||
|
||||
:deep(.el-button--primary) {
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-checkbox__label) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 自定义渐变按钮样式 */
|
||||
.el-button--primary.bg-gradient-to-r {
|
||||
background: linear-gradient(to right, #9333ea, #2563eb) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.el-button--primary.bg-gradient-to-r:hover {
|
||||
background: linear-gradient(to right, #7c3aed, #1d4ed8) !important;
|
||||
p {
|
||||
font-family: PingFang SC;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0;
|
||||
color: #111;
|
||||
margin-top: 38px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user