480 lines
12 KiB
Vue
480 lines
12 KiB
Vue
<template>
|
||
<div>
|
||
<div v-if="visible" class="popup-overlay">
|
||
<div class="popup-content">
|
||
<div class="login-container relative">
|
||
<el-icon class="absolute! right-[0px] top-[0px] 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="phone">
|
||
<el-input v-model="loginForm.phone" 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="handleLoginEmail">
|
||
<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, loginByMobile } from '~/api/login/index'
|
||
import { sendSms } from '~/api/common/index'
|
||
import REFRESHTOKEN from '~/utils/RefreshToken'
|
||
import { handleLoginQQ, handleLoginWechat } from '~/utils/login'
|
||
import useUserStore from '~/store/user'
|
||
const app = useNuxtApp()
|
||
const token = useToken();
|
||
|
||
const userStore = useUserStore()
|
||
const tokenCookie = useCookie<string | undefined>('token');
|
||
|
||
const props = defineProps({
|
||
visible: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
onClose: {
|
||
type: Function,
|
||
default: () => ({}),
|
||
},
|
||
active: {
|
||
type: String,
|
||
default: 'account',
|
||
},
|
||
/** code */
|
||
code: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
/** 区分 */
|
||
type: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
state: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
})
|
||
|
||
const activeTab = ref(props.active)
|
||
const counting = ref(0)
|
||
const formRef = ref<FormInstance>()
|
||
|
||
const loginForm = reactive({
|
||
mobile: '',
|
||
password: '',
|
||
phone: '',
|
||
code: '',
|
||
agreement: false,
|
||
})
|
||
|
||
const rules = {
|
||
mobile: {
|
||
required: true,
|
||
message: '请输入用户名',
|
||
trigger: 'blur',
|
||
},
|
||
password: {
|
||
required: true,
|
||
message: '请输入密码',
|
||
trigger: 'blur',
|
||
},
|
||
phone: [
|
||
{ 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(() => {
|
||
app.$openRegister()
|
||
})
|
||
}
|
||
// 发送验证码
|
||
const handleSendCode = async () => {
|
||
if (counting.value > 0) return
|
||
if (!loginForm.phone) {
|
||
ElMessage.warning('请输入手机号')
|
||
return
|
||
}
|
||
// 这里添加手机号验证逻辑
|
||
const phoneReg = /^1[3-9]\d{9}$/
|
||
if (!phoneReg.test(loginForm.phone)) {
|
||
ElMessage.warning('请输入正确的手机号')
|
||
return
|
||
}
|
||
// 这里添加发送验证码的接口调用
|
||
// TODO: 调用发送验证码接口
|
||
const res = await sendSms({
|
||
mobile: loginForm.phone,
|
||
scene: 1, // 场景值,根据实际情况设置
|
||
})
|
||
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} = res
|
||
if (code === 0) {
|
||
// 设置cookie
|
||
tokenCookie.value = data.accessToken;
|
||
// 更新state
|
||
token.value = data.accessToken;
|
||
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 loginByMobile({
|
||
mobile: loginForm.phone,
|
||
code: loginForm.code,
|
||
socialType: props.type,
|
||
socialCode: props.code,
|
||
socialState: props.state,
|
||
})
|
||
if (res.code === 0) {
|
||
const data = res.data
|
||
REFRESHTOKEN.setToken(data.accessToken, data.refreshToken)
|
||
REFRESHTOKEN.setUserId(data.userId.toString())
|
||
REFRESHTOKEN.setUserName(loginForm.phone)
|
||
userStore.setToken(data.accessToken)
|
||
userStore.setUserId(data.userId.toString())
|
||
userStore.setUserName(loginForm.phone)
|
||
userStore.setRefreshToken(data.refreshToken)
|
||
ElMessage.success('登录成功')
|
||
props.onClose()
|
||
// 获取用户信息
|
||
userStore.getUserInfo()
|
||
// 登录成功
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
}
|
||
|
||
const handleLoginEmail = () => {
|
||
props.onClose()
|
||
nextTick(() => {
|
||
app.$openLoginEmail()
|
||
})
|
||
}
|
||
</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>
|