Files
front-pc/components/kl-email/index.vue
2025-08-26 16:25:58 +08:00

457 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div v-if="visible" class="popup-overlay">
<div class="popup-content">
<div class="login-container relative">
<el-icon class="absolute! right-0 top-0 cursor-pointer" @click="onClose()"><Close /></el-icon>
<!-- 左侧插图 -->
<div class="login-left">
<img src="~/assets/images/login-illustration.png" alt="login" class="login-img" />
</div>
<!-- 右侧登录表单 -->
<div class="login-right">
<h2 class="login-title">邮箱登录</h2>
<!-- 登录方式切换 -->
<div class="login-tabs">
<!-- <span :class="['tab-item', activeTab === 'account' ? 'active' : '']" @click="activeTab = 'account'"> 密码登录 </span> -->
<!-- <span :class="['tab-item', activeTab === 'verify' ? 'active' : '']" @click="activeTab = 'verify'"> 邮箱登录 </span> -->
</div>
<!-- 登录表单 -->
<el-form ref="formRef" :model="loginForm" :rules="rules" class="login-form">
<template v-if="activeTab === 'account'">
<el-form-item prop="mobile">
<el-input v-model="loginForm.mobile" placeholder="请输入用户名" :prefix-icon="User" class="w-322px!" />
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" placeholder="请输入密码" :prefix-icon="Lock" type="password" />
</el-form-item>
</template>
<!-- 验证码登录 -->
<template v-else>
<el-form-item prop="email">
<el-input v-model="loginForm.email" placeholder="请输入邮箱" class="w-322px!" />
</el-form-item>
<el-form-item prop="code">
<div class="verify-code-input">
<el-input v-model="loginForm.code" placeholder="请输入验证码" />
<el-button type="primary" class="verify-code-btn" :disabled="counting > 0" @click="handleSendCode">
{{ counting > 0 ? `${counting}s后重新获取` : '获取验证码' }}
</el-button>
</div>
</el-form-item>
</template>
<!-- 登录按钮 -->
<el-button type="primary" class="login-button" :loading="loading" @click="handleLogin"> 登录 </el-button>
<!-- 用户协议 -->
<div class="agreement">
<el-checkbox v-model="loginForm.agreement"> 我已阅读并同意<span class="link">多多用户协议</span> </el-checkbox>
</div>
<!-- 第三方登录 -->
<div class="third-party-login">
<div class="login-icons" @click="handleLoginQQ">
<img src="~/assets/images/qq-v2.png" alt="QQ登录" class="login-icon" />
<div class="icon-text">QQ登录</div>
</div>
<div class="login-icons" @click="handleLoginWechat">
<img src="~/assets/images/weixin-v2.png" alt="微信登录" class="login-icon" />
<div class="icon-text">微信登录</div>
</div>
<div class="login-icons" @click="goToLogin">
<img src="~/assets/images/email-v2.png" alt="邮箱登录" class="login-icon" />
<div class="icon-text">账号登录</div>
</div>
</div>
<!-- 注册入口 -->
<div class="register-link"> 没有账号<span class="link" @click="handleRegister">点击注册</span> </div>
</el-form>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, nextTick } from 'vue'
import { User, Lock, Close } from '@element-plus/icons-vue'
import type { FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus'
import { login, sendEmailCode, loginByEmail } from '~/api/login/index'
import refreshToken from '~/utils/RefreshToken'
import { handleLoginQQ, handleLoginWechat, generateRandomString } from '~/utils/login'
import useUserStore from '~/stores/user'
const userStore = useUserStore()
const { $openRegister, $openLogin } = useNuxtApp()
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
onClose: {
type: Function,
default: () => ({}),
},
active: {
type: String,
default: 'verify',
},
})
const activeTab = ref(props.active)
const counting = ref(0)
const formRef = ref<FormInstance>()
const loginForm = reactive({
mobile: '',
password: '',
email: '',
code: '',
agreement: false,
})
const rules = {
mobile: {
required: true,
message: '请输入用户名',
trigger: 'blur',
},
password: {
required: true,
message: '请输入密码',
trigger: 'blur',
},
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
// { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' },
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ len: 6, message: '验证码长度应为6位', trigger: 'blur' },
],
}
const handleRegister = () => {
props.onClose()
nextTick(() => {
$openRegister()
})
}
// 发送验证码
const handleSendCode = async () => {
if (counting.value > 0) return
if (!loginForm.email) {
ElMessage.warning('请输入邮箱')
return
}
// 这里添加发送验证码的接口调用
// TODO: 调用发送验证码接口
const res = await sendEmailCode({
email: loginForm.email,
})
if (res.code !== 0) return
if (res.code === 0) {
ElMessage.success('验证码发送成功')
}
// 开始倒计时
counting.value = 60
const timer = setInterval(() => {
counting.value--
if (counting.value <= 0) {
clearInterval(timer)
}
}, 1000)
}
const loading = ref(false)
const handleLogin = async () => {
if (!loginForm.agreement) {
ElMessage.warning('请先同意用户协议')
return
}
// 处理登录逻辑
// 根据不同登录方式处理登录逻辑
if (activeTab.value === 'account') {
await formRef.value?.validate()
// 账号密码登录逻辑
loading.value = true
try {
const res = await login(loginForm)
const { code, data, msg } = res
if (code !== 0) return ElMessage.error(msg)
refreshToken.setToken(data.accessToken, data.refreshToken)
refreshToken.setUserId(data.userId.toString())
refreshToken.setUserName(loginForm.mobile)
userStore.setToken(data.accessToken)
userStore.setUserId(data.userId.toString())
userStore.setUserName(loginForm.mobile)
userStore.setRefreshToken(data.refreshToken)
ElMessage.success('登录成功')
props.onClose()
// 获取用户信息
userStore.getUserInfo()
// 登录成功
} finally {
loading.value = false
}
} else {
// 验证码登录逻辑
try {
await formRef.value?.validate()
// TODO: 调用注册接口
loading.value = true
const res = await loginByEmail({
email: loginForm.email,
code: loginForm.code,
})
if (res.code === 0) {
if (!res.data?.accessToken) {
// 没绑定手机 弹出手机登录界面
ElMessage.error('因你未绑定手机号,请先绑定手机号')
props.onClose()
setTimeout(() => {
$openLogin('verify', loginForm.email, 36, generateRandomString(16))
}, 1200)
return
}
const data = res.data
refreshToken.setToken(data.accessToken, data.refreshToken)
refreshToken.setUserId(data.userId.toString())
refreshToken.setUserName(loginForm.email)
userStore.setToken(data.accessToken)
userStore.setUserId(data.userId.toString())
userStore.setUserName(loginForm.email)
userStore.setRefreshToken(data.refreshToken)
ElMessage.success('登录成功')
props.onClose()
// 获取用户信息
userStore.getUserInfo()
// 登录成功
}
} finally {
loading.value = false
}
}
}
// 跳转到登录
const goToLogin = () => {
props.onClose()
// TODO: 触发切换到登录页面的事件
// emit('switch-to-login')
$openLogin()
}
</script>
<style lang="scss">
.login-dialog {
.el-dialog {
border-radius: 12px;
overflow: hidden;
}
.el-dialog__header {
// margin: 0;
// padding: 0;
}
.el-dialog__body {
padding: 0px !important;
}
}
</style>
<style scoped lang="scss">
.popup-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
.popup-content {
background-color: white;
padding: 20px;
border-radius: 5px;
}
}
.login-container {
display: flex;
height: 508px;
}
.login-left {
display: flex;
align-items: center;
justify-content: center;
background: #f9faff;
width: 480px;
.login-img {
width: 438px;
height: 258px;
}
}
.login-right {
padding: 22px 25px;
box-sizing: border-box;
margin-left: auto;
margin-right: auto;
}
.login-title {
font-size: 20px;
color: #333;
text-align: center;
margin-bottom: 20px;
}
.login-tabs {
display: flex;
justify-content: center;
margin-bottom: 24px;
.tab-item {
font-size: 14px;
color: #666;
padding: 0 16px;
cursor: pointer;
position: relative;
&.active {
color: #1677ff;
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: -4px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 2px;
background: #1677ff;
border-radius: 1px;
}
}
// &:first-child::after {
// content: '';
// position: absolute;
// right: 0;
// top: 50%;
// transform: translateY(-50%);
// width: 1px;
// height: 14px;
// background: #ddd;
// }
}
}
.login-form {
.el-input {
height: 40px;
}
:deep(.el-input__wrapper) {
background: #f5f5f5;
border: none;
box-shadow: none;
}
}
.login-button {
width: 100%;
height: 40px;
margin-top: 24px;
border-radius: 4px;
}
.agreement {
margin: 16px 0;
font-size: 14px;
color: #666;
.link {
color: #1677ff;
cursor: pointer;
}
}
.third-party-login {
display: flex;
justify-content: center;
gap: 40px;
margin-top: 30px;
.login-icons {
text-align: center;
cursor: pointer;
.login-icon {
width: 40px;
height: 40px;
margin-bottom: 8px;
}
.icon-text {
font-size: 12px;
color: #666;
}
}
}
.register-link {
text-align: right;
margin-top: 20px;
font-size: 14px;
color: #666;
.link {
color: #1677ff;
cursor: pointer;
}
}
.verify-code-input {
display: flex;
gap: 12px;
.el-input {
flex: 1;
}
.verify-code-btn {
width: 120px;
height: 40px;
padding: 0;
border-radius: 4px;
&:disabled {
color: #999;
background-color: #f5f5f5;
border-color: #dcdfe6;
}
}
}
// 确保验证码按钮文字不换行
:deep(.verify-code-btn) {
white-space: nowrap;
}
</style>