595 lines
13 KiB
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>
|