Files
PartsInquiry/frontend/pages/auth/login.vue
2025-09-27 22:57:59 +08:00

156 lines
7.2 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="auth-page">
<view class="tabs">
<view :class="['tab', tab==='login'?'active':'']" @click="tab='login'">登录</view>
<view :class="['tab', tab==='register'?'active':'']" @click="tab='register'">注册</view>
<view :class="['tab', tab==='reset'?'active':'']" @click="tab='reset'">忘记密码</view>
</view>
<view v-if="tab==='login'" class="panel">
<input class="input" type="text" v-model.trim="loginForm.email" placeholder="输入邮箱" />
<input class="input" type="password" v-model="loginForm.password" placeholder="输入密码" />
<button class="btn primary" :disabled="loading" @click="onLogin">登录</button>
</view>
<view v-else-if="tab==='register'" class="panel">
<input class="input" type="text" v-model.trim="regForm.name" placeholder="输入用户名" />
<input class="input" type="text" v-model.trim="regForm.email" placeholder="输入邮箱" />
<view class="row">
<input class="input flex1" type="text" v-model.trim="regForm.code" placeholder="邮箱验证码" />
<button class="btn ghost" :disabled="regCountdown>0 || loading" @click="sendRegCode">{{ regCountdown>0? (regCountdown+'s') : '获取验证码' }}</button>
</view>
<input class="input" type="password" v-model="regForm.password" placeholder="输入密码(≥6位)" />
<input class="input" type="password" v-model="regForm.password2" placeholder="再次输入密码" />
<button class="btn primary" :disabled="loading" @click="onRegister">注册新用户</button>
</view>
<view v-else class="panel">
<input class="input" type="text" v-model.trim="resetForm.email" placeholder="输入邮箱" />
<view class="row">
<input class="input flex1" type="text" v-model.trim="resetForm.code" placeholder="邮箱验证码" />
<button class="btn ghost" :disabled="resetCountdown>0 || loading" @click="sendResetCode">{{ resetCountdown>0? (resetCountdown+'s') : '获取验证码' }}</button>
</view>
<input class="input" type="password" v-model="resetForm.password" placeholder="新密码(≥6位)" />
<input class="input" type="password" v-model="resetForm.password2" placeholder="再次输入新密码" />
<button class="btn primary" :disabled="loading" @click="onReset">重置密码</button>
</view>
</view>
</template>
<script>
import { get, post } from '../../common/http.js'
export default {
data(){
return {
loading: false,
tab: 'login',
loginForm: { email: '', password: '' },
regForm: { name: '', email: '', code: '', password: '', password2: '' },
resetForm: { email: '', code: '', password: '', password2: '' },
regCountdown: 0,
resetCountdown: 0,
_timers: []
}
},
beforeUnmount(){ this._timers.forEach(t=>clearInterval(t)) },
methods: {
toast(msg){ try{ uni.showToast({ title: String(msg||'操作失败'), icon: 'none' }) } catch(_){} },
validateEmail(v){ return /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(String(v||'').trim()) },
startCountdown(key){
if (this[key] > 0) return
this[key] = 60
const timer = setInterval(()=>{ this[key] = Math.max(0, this[key]-1); if (this[key]===0) clearInterval(timer) }, 1000)
this._timers.push(timer)
},
async onLogin(){
const { email, password } = this.loginForm
if (!this.validateEmail(email)) return this.toast('请输入正确邮箱')
if (!password || password.length<6) return this.toast('请输入至少6位密码')
this.loading = true
try{
const data = await post('/api/auth/password/login', { email, password })
this.afterLogin(data)
}catch(e){ this.toast(e.message) }
finally{ this.loading=false }
},
afterLogin(data){
try{
if (data && data.token) {
uni.setStorageSync('TOKEN', data.token)
if (data.user && data.user.shopId) uni.setStorageSync('SHOP_ID', data.user.shopId)
uni.setStorageSync('ENABLE_DEFAULT_USER', 'false')
uni.removeStorageSync('DEFAULT_USER_ID')
this.toast('登录成功')
setTimeout(()=>{ uni.reLaunch({ url: '/pages/index/index' }) }, 300)
}else{
this.toast('登录失败')
}
}catch(_){ this.toast('登录失败') }
},
async sendRegCode(){
if (!this.validateEmail(this.regForm.email)) return this.toast('请输入正确邮箱')
this.loading = true
try{
const r = await post('/api/auth/email/send', { email: this.regForm.email, scene: 'register' })
if (r && r.ok) this.startCountdown('regCountdown')
this.toast(r && r.ok ? '验证码已发送' : '发送过于频繁')
}catch(e){ this.toast(e.message) }
finally{ this.loading=false }
},
async onRegister(){
const f = this.regForm
if (!f.name || f.name.trim().length<1) return this.toast('请输入用户名')
if (!this.validateEmail(f.email)) return this.toast('请输入正确邮箱')
if (!f.code) return this.toast('请输入验证码')
if (!f.password || f.password.length<6) return this.toast('密码至少6位')
if (f.password !== f.password2) return this.toast('两次密码不一致')
this.loading = true
try{
const data = await post('/api/auth/email/register', { name: f.name.trim(), email: f.email.trim(), code: f.code.trim(), password: f.password })
this.afterLogin(data)
}catch(e){ this.toast(e.message) }
finally{ this.loading=false }
},
async sendResetCode(){
if (!this.validateEmail(this.resetForm.email)) return this.toast('请输入正确邮箱')
this.loading = true
try{
const r = await post('/api/auth/email/send', { email: this.resetForm.email, scene: 'reset' })
if (r && r.ok) this.startCountdown('resetCountdown')
this.toast(r && r.ok ? '验证码已发送' : '发送过于频繁')
}catch(e){ this.toast(e.message) }
finally{ this.loading=false }
},
async onReset(){
const f = this.resetForm
if (!this.validateEmail(f.email)) return this.toast('请输入正确邮箱')
if (!f.code) return this.toast('请输入验证码')
if (!f.password || f.password.length<6) return this.toast('新密码至少6位')
if (f.password !== f.password2) return this.toast('两次密码不一致')
this.loading = true
try{
const r = await post('/api/auth/email/reset-password', { email: f.email.trim(), code: f.code.trim(), newPassword: f.password, confirmPassword: f.password2 })
if (r && r.ok) { this.toast('已重置请使用新密码登录'); this.tab='login'; this.loginForm.email=f.email; }
else this.toast('重置失败')
}catch(e){ this.toast(e.message) }
finally{ this.loading=false }
}
}
}
</script>
<style lang="scss">
.auth-page{ padding: 32rpx; display:flex; flex-direction: column; gap: 24rpx; }
.tabs{ display:flex; gap: 24rpx; }
.tab{ padding: 12rpx 20rpx; border-radius: 999rpx; background:#f2f4f8; color:#5b6b80; font-weight:700; }
.tab.active{ background:#2d6be6; color:#fff; }
.panel{ display:flex; flex-direction: column; gap: 16rpx; background:#fff; padding: 24rpx; border-radius: 16rpx; border:2rpx solid #eef2f9; }
.input{ background:#f7f9ff; border:2rpx solid rgba(45,107,230,0.12); border-radius: 12rpx; padding: 22rpx 20rpx; font-size: 28rpx; }
.row{ display:flex; gap: 12rpx; align-items:center; }
.flex1{ flex:1; }
.btn{ padding: 22rpx 20rpx; border-radius: 12rpx; font-weight: 800; text-align:center; }
.btn.primary{ background: linear-gradient(135deg, #4788ff 0%, #2d6be6 100%); color:#fff; }
.btn.ghost{ background:#eef3ff; color:#2d6be6; }
</style>