Files
hmc_gbhd/frontend/src/pages/login/index.vue
T
2025-05-06 14:53:56 +08:00

519 lines
13 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>
<view class="login-container">
<view class="title-area">
<div class="tech-animation">
<svg preserveAspectRatio="xMidYMid slice" viewBox="0 0 300 100">
<pattern id="pattern-hex" x="0" y="0" width="10" height="10" patternUnits="userSpaceOnUse"
patternTransform="scale(0.8)">
<rect x="0" y="0" width="10" height="10" fill="none"/>
<path d="M0,5 L2.5,0 L7.5,0 L10,5 L7.5,10 L2.5,10 Z" stroke-width="0.5" stroke="rgba(244, 90, 90, 0.2)"
fill="rgba(244, 90, 90, 0.1)">
<animate attributeName="stroke-opacity" values="0.1;0.3;0.1" dur="3s" repeatCount="indefinite"/>
</path>
</pattern>
<rect x="0" y="0" width="100%" height="100%" fill="url(#pattern-hex)"/>
<g fill="none" stroke-width="0.5">
<path d="M20,50 Q60,20 100,50 T180,50 T260,50" stroke="rgba(244, 90, 90, 0.6)">
<animate attributeName="d"
values="M20,50 Q60,20 100,50 T180,50 T260,50;
M20,50 Q60,80 100,50 T180,50 T260,50;
M20,50 Q60,20 100,50 T180,50 T260,50"
dur="10s" repeatCount="indefinite"/>
</path>
<path d="M20,70 Q60,40 100,70 T180,70 T260,70" stroke="rgba(244, 90, 90, 0.4)">
<animate attributeName="d"
values="M20,70 Q60,40 100,70 T180,70 T260,70;
M20,70 Q60,100 100,70 T180,70 T260,70;
M20,70 Q60,40 100,70 T180,70 T260,70"
dur="12s" repeatCount="indefinite"/>
</path>
</g>
<circle cx="50" cy="50" r="1" fill="rgba(244, 90, 90, 0.8)">
<animate attributeName="cx" values="50;250;50" dur="20s" repeatCount="indefinite"/>
<animate attributeName="cy" values="50;30;80;50" dur="16s" repeatCount="indefinite"/>
<animate attributeName="r" values="1;1.5;1" dur="5s" repeatCount="indefinite"/>
</circle>
<circle cx="150" cy="30" r="1" fill="rgba(244, 90, 90, 0.8)">
<animate attributeName="cx" values="150;100;200;150" dur="25s" repeatCount="indefinite"/>
<animate attributeName="cy" values="30;60;20;30" dur="18s" repeatCount="indefinite"/>
<animate attributeName="r" values="1;1.8;1" dur="6s" repeatCount="indefinite"/>
</circle>
<circle cx="250" cy="70" r="1" fill="rgba(244, 90, 90, 0.8)">
<animate attributeName="cx" values="250;150;250" dur="22s" repeatCount="indefinite"/>
<animate attributeName="cy" values="70;40;70" dur="15s" repeatCount="indefinite"/>
<animate attributeName="r" values="1;1.2;1" dur="4s" repeatCount="indefinite"/>
</circle>
</svg>
</div>
<view class="title-content">
<text class="main-title">HELLO,</text>
<view class="sub-title-container">
<text class="sub-title">欢迎使用</text>
</view>
</view>
</view>
<view class="login-card">
<view class="login-tabs">
<view
:class="['tab', activeMethod === 'phone' ? 'active' : '']"
@click="setActiveMethod('phone')"
>
手机快速登录
</view>
<view
:class="['tab', activeMethod === 'password' ? 'active' : '']"
@click="setActiveMethod('password')"
>
账号密码登录
</view>
</view>
<!-- 登录表单区域 -->
<view class="form-container">
<!-- 手机号码输入框 -->
<view class="input-box">
<t-icon name="mobile" size="20px" class="input-icon"/>
<input
class="input"
type="tel"
v-model="loginForm.phone"
placeholder="请输入手机号"
maxlength="11"
/>
</view>
<!-- 验证码输入框 -->
<view class="input-box">
<t-icon name="shield-error" size="20px" class="input-icon"/>
<input
class="input"
type="number"
v-model="loginForm.smsCode"
placeholder="请输入验证码"
maxlength="6"
/>
<view class="verify-code-btn" @click="getSmsCode" :class="{ disabled: isCountingDown }">
{{ countDownText }}
</view>
</view>
<!-- 登录按钮 -->
<view class="submit-btn" @click="handleLogin">
立即登录
</view>
<!-- 注册链接 -->
<view class="register-link" @click="navigateToRegister">
<text>没有账号?</text>
<text class="link">去注册</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import {ref, reactive, computed, onMounted, onBeforeUnmount} from 'vue'
import {Toast} from 'tdesign-mobile-vue';
import {useRequest} from "@/utils/request";
import {useUserStore} from "@/store";
const request = useRequest()
const userStore = useUserStore()
const activeMethod = ref('phone')
const loginForm = reactive({
phone: '',
smsCode: ''
})
const setActiveMethod = (method) => {
activeMethod.value = method
}
// 倒计时相关
const countdown = ref(0)
const timer = ref(null)
// 本地存储键名
const SMS_COUNTDOWN_KEY = '__SMS_COUNTDOWN__'
const SMS_PHONE_KEY = '__SMS_PHONE__'
// 是否在倒计时中
const isCountingDown = computed(() => countdown.value > 0)
// 验证码按钮文字
const countDownText = computed(() => {
return isCountingDown.value ? `${countdown.value}秒后重试` : '获取验证码'
})
// 保存倒计时状态到本地存储
const saveCountdownState = (seconds, phone) => {
if (seconds <= 0) {
uni.removeStorageSync(SMS_COUNTDOWN_KEY)
uni.removeStorageSync(SMS_PHONE_KEY)
return
}
// 保存结束时间戳和手机号
const endTime = Date.now() + seconds * 1000
uni.setStorageSync(SMS_COUNTDOWN_KEY, endTime)
uni.setStorageSync(SMS_PHONE_KEY, phone)
}
// 从本地存储恢复倒计时状态
const restoreCountdownState = () => {
const endTime = uni.getStorageSync(SMS_COUNTDOWN_KEY)
const savedPhone = uni.getStorageSync(SMS_PHONE_KEY)
if (endTime && savedPhone) {
const now = Date.now()
const remainingSeconds = Math.floor((endTime - now) / 1000)
if (remainingSeconds > 0) {
countdown.value = remainingSeconds
startCountDownTimer() // 重新启动定时器
// 如果用户还没输入手机号,填入保存的手机号
if (!loginForm.phone) {
loginForm.phone = savedPhone
}
} else {
// 倒计时已结束,清除存储
uni.removeStorageSync(SMS_COUNTDOWN_KEY)
uni.removeStorageSync(SMS_PHONE_KEY)
}
}
}
// 开始倒计时定时器
const startCountDownTimer = () => {
if (timer.value) {
clearInterval(timer.value)
}
timer.value = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(timer.value)
timer.value = null
// 清除存储
uni.removeStorageSync(SMS_COUNTDOWN_KEY)
uni.removeStorageSync(SMS_PHONE_KEY)
}
}, 1000)
}
// 开始倒计时
const startCountDown = () => {
countdown.value = 60
saveCountdownState(60, loginForm.phone)
startCountDownTimer()
}
// 在页面挂载时恢复倒计时状态
onMounted(() => {
restoreCountdownState()
})
// 在页面卸载前清除定时器
onBeforeUnmount(() => {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
// 获取验证码
async function getSmsCode() {
if (!loginForm.phone) {
Toast('请输入手机号码');
return
}
// 验证手机号码
if (!/^1[3-9]\d{9}$/.test(loginForm.phone)) {
Toast('请输入正确的手机号码');
return
}
if (isCountingDown.value) return
try {
const {code, data, msg} = await request.post('/client/auth/send-sms-code/' + loginForm.phone)
if (code === 0) {
Toast({message: '验证码发送成功', theme: 'success'})
// 开始倒计时
startCountDown()
} else {
Toast({message: msg, theme: 'error'})
}
} catch (e) {
Toast({message: e.message, theme: 'error'})
}
}
async function handleLogin() {
if (!loginForm.phone) {
Toast('请输入手机号码');
return
}
if (!loginForm.smsCode) {
Toast('请输入验证码');
return
}
try {
userStore.smsLogin(loginForm).then(() => {
userStore.getUserInfo().then(() => {
Toast({message: '登录成功', theme: 'success'})
uni.switchTab({
url: '/pages/index/index'
})
}).catch(e => {
Toast({message: e.message, theme: 'error'})
})
}).catch(e => {
console.log(e)
Toast({message: e.message, theme: 'error'})
})
// const {data, code, msg} = await request.post('/client/auth/sms-login', loginForm)
// if (code === 0) {
// Toast({message: '登录成功', theme: 'success'})
// // 保存token
// uni.setStorageSync('token', data)
//
// // 获取之前保存的重定向URL
// const redirectUrl = uni.getStorageSync('REDIRECT_URL') || '/pages/index/index'
// uni.removeStorageSync('REDIRECT_URL') // 使用后删除
//
// // 延迟跳转,让用户看到成功提示
// setTimeout(() => {
// if (redirectUrl.startsWith('/pages/index/') || redirectUrl.startsWith('/pages/profile/')) {
// // 主页和个人中心,使用switchTab
// uni.switchTab({
// url: redirectUrl
// })
// } else {
// // 其他页面使用reLaunch
// uni.reLaunch({
// url: redirectUrl
// })
// }
// }, 1000)
// } else {
// Toast({message: msg, theme: 'error'})
// }
} catch (e) {
console.error(e)
Toast({message: e.message, theme: 'error'})
}
}
function navigateToRegister(){
uni.redirectTo({
url: '/pages/login/register'
})
}
</script>
<style lang="less" scoped>
body {
height: 100%;
overflow: hidden;
position: fixed;
width: 100%;
top: 0;
left: 0;
}
.login-container {
height: 100vh;
background: #f4f2f2;
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
}
.title-area {
padding: 0;
height: 30vh;
position: relative;
overflow: hidden;
display: flex;
align-items: center;
background: linear-gradient(135deg, #7a1818 0%, #d03e3e 50%, #f45a5a 100%);
}
.tech-animation {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.tech-animation svg {
width: 100%;
height: 100%;
}
.title-content {
padding: 0 30px;
position: relative;
z-index: 1;
}
.main-title {
font-size: 36px;
font-weight: bold;
margin-bottom: 8px;
display: block;
position: relative;
z-index: 1;
color: #ffffff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.sub-title-container {
display: flex;
align-items: center;
position: relative;
z-index: 1;
}
.sub-title {
font-size: 18px;
color: #ffffff;
position: relative;
z-index: 1;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.login-card {
flex: 1;
background-color: #ffffff;
border-radius: 30px 30px 0 0;
margin-top: -30px;
padding: 30px 0 0;
position: relative;
z-index: 2;
box-shadow: 0 -5px 20px rgba(0, 0, 0, 0.05);
}
.login-tabs {
display: flex;
justify-content: space-between;
margin-bottom: 24px;
position: relative;
gap: 10px;
padding: 0 30px;
}
.tab {
width: calc(50% - 5px);
height: 55px;
display: flex;
justify-content: center;
align-items: center;
font-size: 15px;
color: #f45a5a;
font-weight: 500;
background-color: #fff0f0;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.tab:first-child {
border-radius: 25px 15px 30px 25px;
clip-path: polygon(0 0, 100% 0, 90% 100%, 0 100%);
}
.tab:last-child {
border-radius: 30px 25px 25px 15px;
clip-path: polygon(10% 0, 100% 0, 100% 100%, 0 100%);
}
.tab.active {
color: #fff;
background-color: #f45a5a;
}
.form-container {
padding: 0 30px;
}
.input-box {
height: 50px;
background-color: #eceaea;
border-radius: 25px;
display: flex;
align-items: center;
padding: 0 15px;
margin-bottom: 16px;
}
.input-icon {
color: #999;
margin-right: 10px;
}
.input {
flex: 1;
height: 100%;
border: none;
font-size: 15px;
background-color: transparent;
}
.input::placeholder {
color: #bcbcbc;
}
.verify-code-btn {
color: #f45a5a;
font-size: 14px;
white-space: nowrap;
&.disabled {
color: #999;
}
}
.submit-btn {
margin-top: 32px;
height: 50px;
background-color: #f45a5a;
border-radius: 25px;
color: white;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.register-link {
margin-top: 16px;
text-align: center;
font-size: 28rpx;
color: #666;
.link {
color: #f45a5a;
margin-left: 4px;
}
}
</style>