flaunt-web/src/components/LoginDialog.vue

595 lines
13 KiB
Vue

<template>
<BaseDialog v-model="visible" width="400px">
<div class="login-wrapper">
<div class="login-title">
<h2 style="position: relative;z-index: 1;">登录</h2>
</div>
<div class="form-container">
<template v-if="loginType === 'phone'">
<div class="input-box" style="margin-top: 98px;">
<div class="phone-prefix">
<span>+86</span>
<div class="divider"></div>
</div>
<input type="text" v-model="form.phone" placeholder="请输入手机号" class="native-input" />
</div>
<div class="code-box-row">
<div class="input-box code-input-box">
<input type="text" v-model="form.code" placeholder="请输入验证码" class="native-input" />
</div>
<button class="send-code-btn" type="button" :disabled="sendingCode || countdown > 0" @click="sendCode">
<span class="text">{{ countdown > 0 ? `${countdown}s` : (sendingCode ? '发送中...' : '发送验证码') }}</span>
</button>
</div>
<div class="error-text" v-show="showError">请输入正确验证码</div>
</template>
<template v-else>
<div class="qrcode-wrapper">
<div class="qrcode-img">
<img v-if="qrcode" :src="qrcode" style="width: 100%; height: 100%; object-fit: cover;" alt="微信扫码登录" />
</div>
<!-- <img class="qrcode-img" src="/src/public/login/qrcode.png" alt="微信登录二维码" /> -->
<div class="qr-text">
请使用微信扫描二维码登录<br>
“装样大师” 网页版
</div>
</div>
</template>
<div class="agreement-row" @click="form.agreed = !form.agreed"
:style="{ 'margin-top': loginType === 'phone' ? '54px' : '0px' }">
<div class="check-icon" :class="{ 'is-checked': form.agreed }">
<img v-if="form.agreed" class="empty-circle" src="/src/public/login/agreement.png">
<img v-else class="empty-circle" src="/src/public/login/unAgreement.png">
</div>
<span class="agreement-text">我已阅读并同意<a @click.stop>《用户协议》</a>和<a @click.stop>《隐私政策》</a></span>
</div>
<button class="submit-btn" :class="{ 'is-loading': loading }" @click="handleLogin">登录</button>
<div class="other-methods">
<div class="line"></div>
<div class="text">其他登录方式</div>
<div class="line2"></div>
</div>
<div class="wechat-icon" @click="toggleLoginType">
<img class="login-style" src="/src/public/login/wechat-login.png" v-if="loginType === 'phone'" />
<img class="login-style" src="/src/public/login/phone-login.png" v-else />
</div>
</div>
</div>
</BaseDialog>
</template>
<script setup>
import { ref, reactive, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Close } from '@element-plus/icons-vue'
import api from '@/api'
import authService from '@/api/authService'
import { useUserStore } from '@/stores/user'
import BaseDialog from './BaseDialog.vue'
const emit = defineEmits(['success'])
const userStore = useUserStore()
const visible = ref(false)
const loading = ref(false)
const sendingCode = ref(false)
const countdown = ref(0)
let timer = null
let qrTimer = null
const qrcode = ref('')
const qrKey = ref('')
const showError = ref(false)
const loginType = ref('phone')
const form = reactive({ phone: '', code: '', agreed: false })
function open() {
visible.value = true
loginType.value = 'phone'
form.phone = localStorage.getItem('remember_phone') || ''
form.code = ''
form.agreed = false
showError.value = false
fetchQrCode()
}
async function fetchQrCode() {
try {
// 确保请求二维码前本地有 Token (若缺失则自动拉取游客 Token)
if (!localStorage.getItem('token')) {
const configRes = await authService.getUserConfig()
if (configRes && configRes.data && configRes.data.token) {
localStorage.setItem('token', configRes.data.token)
}
}
const res = await authService.getLoginCode()
if (res && res.data) {
qrKey.value = res.data.key
qrcode.value = res.data.qrcode
if (qrTimer) clearInterval(qrTimer)
qrTimer = setInterval(checkQrLogin, 2000)
}
} catch (err) {
console.error('获取微信登录二维码失败:', err)
}
}
async function checkQrLogin() {
if (loginType.value !== 'wechat' || !visible.value) return
try {
const res = await authService.askLoginCode({ key: qrKey.value })
if (res && res.code === 0 && res.data && res.data.token) {
localStorage.setItem('token', res.data.token)
if (qrTimer) {
clearInterval(qrTimer)
qrTimer = null
}
visible.value = false
emit('success')
ElMessage.success('微信登录成功')
window.location.reload()
}
} catch (err) {
// 忽略轮询中的错误
}
}
function toggleLoginType() {
loginType.value = loginType.value === 'phone' ? 'wechat' : 'phone'
}
function startCountdown() {
countdown.value = 60
if (timer) clearInterval(timer)
timer = setInterval(() => {
if (countdown.value > 1) {
countdown.value--
} else {
countdown.value = 0
clearInterval(timer)
timer = null
}
}, 1000)
}
const smsTimestamp = ref('')
async function sendCode() {
if (sendingCode.value || countdown.value > 0) return
const phoneReg = /^1[3-9]\d{9}$/
if (!form.phone) {
ElMessage.warning('请输入手机号')
return
}
if (!phoneReg.test(form.phone)) {
ElMessage.warning('请输入正确的11位手机号')
return
}
// 点击发送验证码时清空验证码,并记住手机号
form.code = ''
showError.value = false
if (form.phone) {
localStorage.setItem('remember_phone', form.phone)
}
sendingCode.value = true
try {
const res = await authService.sendCode({ phone: form.phone })
if (res && res.data && res.data.timestamp) {
smsTimestamp.value = res.data.timestamp
}
ElMessage.success(res?.message || '验证码发送成功')
startCountdown()
} catch (err) {
console.error('发送验证码失败:', err)
} finally {
sendingCode.value = false
}
}
onUnmounted(() => {
if (timer) {
clearInterval(timer)
timer = null
}
if (qrTimer) {
clearInterval(qrTimer)
qrTimer = null
}
})
async function handleLogin() {
showError.value = false
if (!form.phone || !form.code) {
showError.value = true
return
}
if (!form.agreed) {
ElMessage.warning('请先阅读并同意用户协议和隐私政策')
return
}
loading.value = true
try {
const loginPayload = {
type: 'phone',
data: {
phone: form.phone,
code: form.code,
timestamp: smsTimestamp.value
}
}
const res = await authService.login(loginPayload)
if (res && res.data && res.data.token) {
localStorage.setItem('token', res.data.token)
userStore.setUser(res.data, form.phone)
console.log("登录成功", res);
ElMessage.success('登录成功')
visible.value = false
emit('success')
// window.location.reload()
} else {
showError.value = true
}
} catch (err) {
showError.value = true
console.error('手机验证码登录失败:', err)
} finally {
loading.value = false
}
}
async function handleWechatLogin() {
// try {
// const res = await api.auth.wechatLoginUrl()
// if (res.oauth_url) {
// window.location.href = res.oauth_url
// // }
// } catch (err) {
// ElMessage.warning('微信登录暂不可用,请联系管理员')
// }
}
defineExpose({ open })
</script>
<style lang="scss">
.image-style-login-dialog {
border-radius: 20px !important;
background: #ffffff !important;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1) !important;
position: relative;
overflow: visible !important;
.el-dialog__header {
display: none !important;
}
.el-dialog__body {
padding: 0 !important;
overflow: visible !important;
}
}
</style>
<style lang="scss" scoped>
.custom-close-btn {
position: absolute;
top: -45px;
right: -5px;
width: 32px;
height: 32px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 16px;
cursor: pointer;
z-index: 999;
&:hover {
background: rgba(255, 255, 255, 0.5);
}
}
.login-wrapper {
padding: 6px 30px 40px;
display: flex;
flex-direction: column;
align-items: center;
height: 500px;
}
.login-title {
// margin-bottom: 98px;
position: relative;
text-align: center;
::before {
content: '';
display: inline-block;
width: 120px;
height: 33px;
background-image: url('@/public/index/decoration1.png');
background-size: 100%;
background-repeat: no-repeat;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: -24px;
opacity: 0.5;
z-index: 0;
}
h2 {
margin: 0;
letter-spacing: 2px;
font-weight: 700;
font-size: 30px;
color: #1A1A1A;
line-height: 30px;
letter-spacing: 2px;
}
.title-deco {
position: absolute;
bottom: -15px;
left: 50%;
transform: translateX(-50%);
width: 80px;
height: 24px;
pointer-events: none;
}
}
.form-container {
width: 100%;
}
.input-box {
background: #D8D8D850;
border-radius: 12px;
height: 50px;
display: flex;
align-items: center;
padding: 0 16px;
margin-bottom: 18px;
.phone-prefix {
display: flex;
align-items: center;
color: #3D3D3D;
font-size: 14px;
.divider {
width: 1px;
height: 29px;
background: #CECBDC;
margin: 0 12px;
}
}
.native-input {
flex: 1;
border: none;
background: transparent;
outline: none;
font-size: 16px;
color: #1A1A1A;
width: 100%;
&::placeholder {
color: #767676;
}
}
}
.code-box-row {
display: flex;
align-items: center;
gap: 8px;
height: 52px;
.code-input-box {
flex: 1;
margin-bottom: 0;
}
.send-code-btn {
width: 110px;
height: 50px;
border-radius: 12px;
border: 1px solid #D8D8D8;
background: #fff;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
flex-shrink: 0;
outline: none;
.text {
font-weight: 400;
font-size: 16px;
text-align: left;
font-style: normal;
text-transform: none;
background: linear-gradient(124.63845108274714deg, #A679F4 7%, #778CF5 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
&:hover {
background: #fcfaff;
border-color: #9254ff;
}
&:disabled {
cursor: not-allowed;
opacity: 0.6;
border-color: #e4e4e4;
background: #f5f5f5;
pointer-events: none;
.text {
background: none;
-webkit-text-fill-color: #999;
color: #999;
}
}
}
}
.error-text {
font-size: 12px;
color: #FF4D4F;
line-height: 10px;
margin-top: 8px;
margin-bottom: -10px;
padding-left: 15px;
}
.agreement-row {
display: flex;
align-items: center;
cursor: pointer;
.check-icon {
width: 15px;
height: 15px;
border-radius: 50%;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.empty-circle {
width: 16px;
height: 16px;
}
}
.agreement-text {
font-size: 12px;
color: #AAAAAA;
a {
color: #767676;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
.submit-btn {
width: 100%;
height: 56px;
border-radius: 27px;
background: linear-gradient(90deg, #778CF5 0%, #A679F4 100%);
font-weight: 400;
font-size: 16px;
color: #FFFFFF;
line-height: 20px;
border: none;
margin-top: 8px;
cursor: pointer;
outline: none;
&:hover {
opacity: 0.95;
}
}
.other-methods {
display: flex;
align-items: center;
justify-content: center;
margin-top: 14px;
gap: 12px;
.line {
width: 60px;
height: 2px;
background: linear-gradient(90deg, #FFFFFF 0%, #E5E5E5 100%);
}
.line2 {
width: 60px;
height: 2px;
background: linear-gradient(90deg, #E5E5E5 0%, #FFFFFF 100%);
}
.text {
font-size: 14px;
color: #767676;
line-height: 10px;
}
}
.qrcode-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 40px;
margin-bottom: 20px;
.qrcode-img {
width: 150px;
height: 150px;
margin-bottom: 18px;
object-fit: cover;
}
.qr-text {
font-size: 14px;
color: #1A1A1A;
line-height: 22px;
text-align: center;
}
}
.wechat-icon {
width: 48px;
height: 48px;
border-radius: 50%;
background: linear-gradient(135deg, #7c88ff 0%, #b77bff 100%);
display: flex;
align-items: center;
justify-content: center;
margin: 15px auto 0;
cursor: pointer;
box-shadow: 0 4px 12px rgba(163, 113, 255, 0.2);
.login-style {
width: 50px;
height: 50px;
}
&:hover {
transform: translateY(-2px);
}
}
</style>