Add new components for login and comment functionality

This commit is contained in:
wangqiao
2025-08-17 20:15:33 +08:00
parent 99df1d1f81
commit 07b4d3de99
37 changed files with 4744 additions and 263 deletions

View File

@ -0,0 +1,456 @@
<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 '@/store/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>