2
This commit is contained in:
@@ -1,418 +1,155 @@
|
||||
<template>
|
||||
<view class="login-container">
|
||||
<!-- 背景装饰 -->
|
||||
<view class="background-decoration">
|
||||
<view class="circle circle-1"></view>
|
||||
<view class="circle circle-2"></view>
|
||||
<view class="circle circle-3"></view>
|
||||
</view>
|
||||
|
||||
<!-- 主要内容卡片 -->
|
||||
<view class="login-card">
|
||||
<!-- 顶部Logo区域 -->
|
||||
<view class="header-section">
|
||||
<view class="logo-container">
|
||||
<view class="logo-icon">
|
||||
<svg viewBox="0 0 24 24" class="icon">
|
||||
<path d="M12 2C13.1 2 14 2.9 14 4C14 5.1 13.1 6 12 6C10.9 6 10 5.1 10 4C10 2.9 10.9 2 12 2ZM21 9V7L15 4V6C15 7.66 13.66 9 12 9S9 7.66 9 6V4L3 7V9C3 10.1 3.9 11 5 11V17C5 18.1 5.9 19 7 19H9C9 20.1 9.9 21 11 21H13C14.1 21 15 20.1 15 19H17C18.1 19 19 18.1 19 17V11C20.1 11 21 10.1 21 9Z"/>
|
||||
</svg>
|
||||
</view>
|
||||
<text class="app-name">配件询价</text>
|
||||
</view>
|
||||
<text class="welcome-text">欢迎回来</text>
|
||||
<text class="subtitle">请登录您的账户</text>
|
||||
</view>
|
||||
<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 class="form-section">
|
||||
<view class="input-group">
|
||||
<view class="input-container" :class="{ focused: phoneFocused, filled: form.phone }">
|
||||
<view class="input-icon">
|
||||
<svg viewBox="0 0 24 24" class="icon">
|
||||
<path d="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z"/>
|
||||
</svg>
|
||||
</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model.trim="form.phone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
@focus="phoneFocused = true"
|
||||
@blur="phoneFocused = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="input-group">
|
||||
<view class="input-container" :class="{ focused: passwordFocused, filled: form.password }">
|
||||
<view class="input-icon">
|
||||
<svg viewBox="0 0 24 24" class="icon">
|
||||
<path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"/>
|
||||
</svg>
|
||||
</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model.trim="form.password"
|
||||
password
|
||||
placeholder="请输入密码"
|
||||
@focus="passwordFocused = true"
|
||||
@blur="passwordFocused = false"
|
||||
/>
|
||||
</view>
|
||||
</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 class="actions-section">
|
||||
<button class="login-button" @click="onLogin">
|
||||
<text class="button-text">登录</text>
|
||||
</button>
|
||||
<button class="register-button" @click="onGoRegister">
|
||||
<text class="button-text">注册新账户</text>
|
||||
</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 class="footer-section"></view>
|
||||
</view>
|
||||
</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 { post } from '../../common/http.js'
|
||||
import { get, post } from '../../common/http.js'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: { phone: '', password: '' },
|
||||
phoneFocused: false,
|
||||
passwordFocused: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validate() {
|
||||
const p = String(this.form.phone||'').trim()
|
||||
const okPhone = /^1[3-9]\d{9}$/.test(p)
|
||||
if (!okPhone) { uni.showToast({ title: '请输入正确的手机号', icon: 'none' }); return false }
|
||||
return true
|
||||
},
|
||||
async onLogin() {
|
||||
if (!this.validate()) return
|
||||
try {
|
||||
const phone = String(this.form.phone||'').trim()
|
||||
const password = String(this.form.password||'')
|
||||
const res = await post('/api/auth/password/login', { phone, password })
|
||||
// 统一存储 TOKEN 并跳转首页
|
||||
if (res && res.token) {
|
||||
uni.setStorageSync('TOKEN', res.token)
|
||||
if (res.user && res.user.phone) uni.setStorageSync('USER_MOBILE', res.user.phone)
|
||||
uni.showToast({ title: '登录成功', icon: 'none' })
|
||||
setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }) }, 200)
|
||||
}
|
||||
} catch(e) {
|
||||
uni.showToast({ title: (e && e.message) || '登录失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
onGoRegister() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/auth/register'
|
||||
})
|
||||
}
|
||||
}
|
||||
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">
|
||||
// 主容器
|
||||
.login-container {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 40rpx 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 背景装饰圆形
|
||||
.background-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
|
||||
&.circle-1 {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&.circle-2 {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
top: 60%;
|
||||
right: 15%;
|
||||
animation: float 8s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
&.circle-3 {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
bottom: 20%;
|
||||
left: 20%;
|
||||
animation: float 5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0px); }
|
||||
50% { transform: translateY(-20px); }
|
||||
}
|
||||
|
||||
// 主卡片容器
|
||||
.login-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 90%;
|
||||
max-width: 680rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20rpx);
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx 50rpx;
|
||||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.1);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
// 头部区域
|
||||
.header-section {
|
||||
text-align: center;
|
||||
margin-bottom: 50rpx;
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.logo-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #2d3748;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-text {
|
||||
display: block;
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: #2d3748;
|
||||
margin-bottom: 8rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #718096;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
// 表单区域
|
||||
.form-section {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
.input-container {
|
||||
position: relative;
|
||||
background: #f7fafc;
|
||||
border: 2rpx solid #e2e8f0;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&.focused {
|
||||
border-color: #667eea;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 6rpx rgba(102, 126, 234, 0.1);
|
||||
transform: translateY(-2rpx);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
background: #ffffff;
|
||||
border-color: #cbd5e0;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50rpx;
|
||||
margin-left: 20rpx;
|
||||
|
||||
.icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
fill: #a0aec0;
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&.focused .input-icon .icon {
|
||||
fill: #667eea;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 24rpx 20rpx 24rpx 12rpx;
|
||||
font-size: 32rpx;
|
||||
color: #2d3748;
|
||||
|
||||
&::placeholder {
|
||||
color: #a0aec0;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮区域
|
||||
.actions-section {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:active {
|
||||
transform: translateY(2rpx);
|
||||
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.2) 0%, transparent 50%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
&:active::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.register-button {
|
||||
width: 100%;
|
||||
height: 86rpx;
|
||||
background: transparent;
|
||||
border: 2rpx solid #e2e8f0;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:active {
|
||||
background: #f7fafc;
|
||||
border-color: #cbd5e0;
|
||||
transform: translateY(1rpx);
|
||||
}
|
||||
|
||||
.button-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #718096;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 页脚区域
|
||||
.footer-section {
|
||||
text-align: center;
|
||||
|
||||
.hint-text {
|
||||
font-size: 24rpx;
|
||||
color: #a0aec0;
|
||||
line-height: 1.5;
|
||||
background: rgba(160, 174, 192, 0.1);
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid rgba(160, 174, 192, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 750rpx) {
|
||||
.login-card {
|
||||
margin: 20rpx;
|
||||
padding: 50rpx 30rpx 40rpx;
|
||||
}
|
||||
|
||||
.header-section .welcome-text {
|
||||
font-size: 42rpx;
|
||||
}
|
||||
}
|
||||
.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>
|
||||
|
||||
|
||||
|
||||
@@ -63,29 +63,46 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号 -->
|
||||
<!-- 邮箱 -->
|
||||
<view class="input-group">
|
||||
<view class="input-container" :class="{ focused: phoneFocused, filled: form.phone }">
|
||||
<view class="input-container" :class="{ focused: emailFocused, filled: form.email }">
|
||||
<view class="input-icon">
|
||||
<svg viewBox="0 0 24 24" class="icon">
|
||||
<path d="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38L15.41,15.18C15.69,14.9 16.08,14.82 16.43,14.93C17.55,15.3 18.75,15.5 20,15.5A1,1 0 0,1 21,16.5V20A1,1 0 0,1 20,21A17,17 0 0,1 3,4A1,1 0 0,1 4,3H7.5A1,1 0 0,1 8.5,4C8.5,5.25 8.7,6.45 9.07,7.57C9.18,7.92 9.1,8.31 8.82,8.59L6.62,10.79Z"/>
|
||||
<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm-1 4l-7 4-7-4V6l7 4 7-4v2z"/>
|
||||
</svg>
|
||||
</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model.trim="form.phone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
@focus="phoneFocused = true"
|
||||
@blur="phoneFocused = false"
|
||||
v-model.trim="form.email"
|
||||
type="text"
|
||||
placeholder="请输入邮箱地址"
|
||||
@focus="emailFocused = true"
|
||||
@blur="emailFocused = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<view class="input-group">
|
||||
<view class="input-container" :class="{ focused: codeFocused, filled: form.code }">
|
||||
<view class="input-icon">
|
||||
<svg viewBox="0 0 24 24" class="icon">
|
||||
<path d="M3 10h18v2H3v-2zm0 6h12v2H3v-2zM3 6h18v2H3V6z"/>
|
||||
</svg>
|
||||
</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model.trim="form.code"
|
||||
type="number" maxlength="6"
|
||||
placeholder="请输入6位验证码"
|
||||
@focus="codeFocused = true"
|
||||
@blur="codeFocused = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 密码 -->
|
||||
<view class="input-group">
|
||||
<view class="input-container" :class="{ focused: passwordFocused, filled: form.password }">
|
||||
<view class="input-container" :class="{ focused: pwdFocused, filled: form.password }">
|
||||
<view class="input-icon">
|
||||
<svg viewBox="0 0 24 24" class="icon">
|
||||
<path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"/>
|
||||
@@ -95,30 +112,16 @@
|
||||
class="input-field"
|
||||
v-model.trim="form.password"
|
||||
password
|
||||
placeholder="请输入密码(至少6位)"
|
||||
@focus="passwordFocused = true"
|
||||
@blur="passwordFocused = false"
|
||||
placeholder="请设置登录密码(至少6位)"
|
||||
@focus="pwdFocused = true"
|
||||
@blur="pwdFocused = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 确认密码 -->
|
||||
<!-- 发送验证码按钮 -->
|
||||
<view class="input-group">
|
||||
<view class="input-container" :class="{ focused: confirmPasswordFocused, filled: form.confirmPassword }">
|
||||
<view class="input-icon">
|
||||
<svg viewBox="0 0 24 24" class="icon">
|
||||
<path d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"/>
|
||||
</svg>
|
||||
</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model.trim="form.confirmPassword"
|
||||
password
|
||||
placeholder="请再次输入密码"
|
||||
@focus="confirmPasswordFocused = true"
|
||||
@blur="confirmPasswordFocused = false"
|
||||
/>
|
||||
</view>
|
||||
<button class="login-button" :disabled="countdown>0 || sending" @click="sendCode">{{ btnText }}</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -148,39 +151,70 @@ export default {
|
||||
form: {
|
||||
shopName: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
email: '',
|
||||
code: '',
|
||||
password: ''
|
||||
},
|
||||
shopNameFocused: false,
|
||||
nameFocused: false,
|
||||
phoneFocused: false,
|
||||
passwordFocused: false,
|
||||
confirmPasswordFocused: false
|
||||
emailFocused: false,
|
||||
codeFocused: false,
|
||||
pwdFocused: false,
|
||||
countdown: 0,
|
||||
timer: null,
|
||||
sending: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
btnText(){
|
||||
if (this.countdown > 0) return `${this.countdown}s`
|
||||
if (this.sending) return '发送中...'
|
||||
return '获取验证码'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validate() {
|
||||
const phone = String(this.form.phone || '').trim();
|
||||
const ok = /^1[3-9]\d{9}$/.test(phone);
|
||||
if (!ok) { uni.showToast({ title: '请输入正确的手机号', icon: 'none' }); return false }
|
||||
if (!this.form.password) { uni.showToast({ title: '请输入密码', icon: 'none' }); return false }
|
||||
if (this.form.password.length < 6) { uni.showToast({ title: '密码至少6位', icon: 'none' }); return false }
|
||||
if (!this.form.confirmPassword) { uni.showToast({ title: '请确认密码', icon: 'none' }); return false }
|
||||
if (this.form.password !== this.form.confirmPassword) { uni.showToast({ title: '两次密码不一致', icon: 'none' }); return false }
|
||||
const email = String(this.form.email || '').trim();
|
||||
const ok = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(email);
|
||||
if (!ok) { uni.showToast({ title: '请输入正确的邮箱地址', icon: 'none' }); return false }
|
||||
if (!/^\d{6}$/.test(String(this.form.code||'').trim())) { uni.showToast({ title: '验证码格式不正确', icon: 'none' }); return false }
|
||||
if (String(this.form.password||'').length < 6) { uni.showToast({ title: '密码至少6位', icon: 'none' }); return false }
|
||||
return true;
|
||||
},
|
||||
|
||||
startCountdown(sec){
|
||||
this.countdown = sec
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
this.timer = setInterval(() => {
|
||||
if (this.countdown<=1) { clearInterval(this.timer); this.timer=null; this.countdown=0; return }
|
||||
this.countdown--
|
||||
}, 1000)
|
||||
},
|
||||
async sendCode(){
|
||||
if (this.sending || this.countdown>0) return
|
||||
const e = String(this.form.email||'').trim()
|
||||
const ok = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/.test(e)
|
||||
if (!ok) return uni.showToast({ title: '请输入正确的邮箱地址', icon: 'none' })
|
||||
this.sending = true
|
||||
try {
|
||||
const res = await post('/api/auth/email/send', { email: e, scene: 'login' })
|
||||
const cd = Number(res && res.cooldownSec || 60)
|
||||
this.startCountdown(cd)
|
||||
uni.showToast({ title: '验证码已发送', icon: 'none' })
|
||||
} catch(e) {
|
||||
const msg = (e && e.message) || '发送失败'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
} finally { this.sending=false }
|
||||
},
|
||||
async onRegister() {
|
||||
if (!this.validate()) return;
|
||||
const phone = String(this.form.phone||'').trim();
|
||||
const email = String(this.form.email||'').trim();
|
||||
const name = String(this.form.name||'').trim();
|
||||
const password = String(this.form.password||'');
|
||||
try {
|
||||
const data = await post('/api/auth/register', { phone, name: name || undefined, password });
|
||||
const data = await post('/api/auth/email/register', { email, code: String(this.form.code||'').trim(), name, password: String(this.form.password||'') });
|
||||
if (data && data.token) {
|
||||
uni.setStorageSync('TOKEN', data.token)
|
||||
if (data.user && data.user.phone) uni.setStorageSync('USER_MOBILE', data.user.phone)
|
||||
if (data.user && data.user.email) uni.setStorageSync('USER_EMAIL', data.user.email)
|
||||
if (name) try { uni.setStorageSync('USER_NAME', name) } catch(_) {}
|
||||
uni.showToast({ title: '注册成功', icon: 'none' })
|
||||
setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }) }, 300)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user