Files
PartsInquiry/frontend/pages/auth/login.vue
2025-09-29 21:38:32 +08:00

183 lines
8.6 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="login-hero">
<image class="login-hero-img" :src="authLoginTopImage" mode="widthFix" />
</view>
<view class="header"><text class="title">邮箱密码登录</text></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 class="quick-inline">
<button class="quick-link" @click="gotoRegister">注册</button>
<button class="quick-link" @click="gotoReset">忘记密码</button>
</view>
</view>
<view class="panel minor" v-if="tab==='register'">
<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 class="panel minor" v-if="tab==='reset'">
<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'
import { AUTH_LOGIN_TOP_IMAGE } from '../../common/config.js'
export default {
data(){
return {
loading: false,
tab: 'login',
authLoginTopImage: AUTH_LOGIN_TOP_IMAGE,
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: {
gotoRegister(){ this.tab='register' },
gotoReset(){ this.tab='reset' },
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">
@import '../../uni.scss';
.auth-page{ padding: 32rpx; display:flex; flex-direction: column; gap: 24rpx; position: relative; min-height: 100vh; }
.header{ display:flex; align-items:center; justify-content:center; padding: 8rpx 0 0; }
.title{ font-size: 34rpx; font-weight: 800; color: $uni-text-color; }
.login-hero{ display:flex; justify-content:center; padding: 16rpx 0 0; }
.login-hero-img{ width: 72%; max-width: 560rpx; border-radius: 8rpx; }
.panel{ display:flex; flex-direction: column; gap: 16rpx; background: transparent; padding: 0; border-radius: 0; border: none; }
.panel.minor{ margin-top: 12rpx; }
.input{ background:#ffffff; border:2rpx solid #e5e7eb; 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; border: 1rpx solid rgba(45,107,230,0.25); width: 72%; margin: 0 auto; padding: 14rpx 16rpx; }
.btn.ghost{ background:#eef3ff; color:#2d6be6; }
/* 右下角快捷入口:贴着登录功能,无边框、无背景 */
.quick-inline{ display:flex; gap: 28rpx; justify-content:flex-end; align-items:center; margin-top: 10rpx; }
.quick-link{ background: transparent !important; color: #2d6be6; border: none !important; outline: none; padding: 0; font-size: 26rpx; font-weight: 700; box-shadow: none; line-height: 1.2; }
.quick-link::after{ border: none !important; }
/* 注册/重置页:验证码按钮与左侧输入框等高,且更紧凑 */
.panel.minor .row > .input{ height: $app-form-control-height; padding: 0 $app-form-control-padding-x; }
.panel.minor .row > .btn.ghost{
height: $app-form-control-height;
padding: 0 $app-form-control-padding-x;
border-radius: $app-form-control-border-radius;
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>