541 lines
18 KiB
Vue
541 lines
18 KiB
Vue
<template>
|
||
<view class="register-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="register-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="form-section">
|
||
<!-- 店铺名称 -->
|
||
<view class="input-group">
|
||
<view class="input-container" :class="{ focused: shopNameFocused, filled: form.shopName }">
|
||
<view class="input-icon">
|
||
<svg viewBox="0 0 24 24" class="icon">
|
||
<path d="M12,3L2,12H5V20H19V12H22L12,3M12,8.75A2.25,2.25 0 0,1 14.25,11A2.25,2.25 0 0,1 12,13.25A2.25,2.25 0 0,1 9.75,11A2.25,2.25 0 0,1 12,8.75Z"/>
|
||
</svg>
|
||
</view>
|
||
<input
|
||
class="input-field"
|
||
v-model.trim="form.shopName"
|
||
type="text"
|
||
placeholder="请输入店铺名称"
|
||
@focus="shopNameFocused = true"
|
||
@blur="shopNameFocused = false"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 姓名 -->
|
||
<view class="input-group">
|
||
<view class="input-container" :class="{ focused: nameFocused, filled: form.name }">
|
||
<view class="input-icon">
|
||
<svg viewBox="0 0 24 24" class="icon">
|
||
<path d="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z"/>
|
||
</svg>
|
||
</view>
|
||
<input
|
||
class="input-field"
|
||
v-model.trim="form.name"
|
||
type="text"
|
||
placeholder="请输入您的姓名"
|
||
@focus="nameFocused = true"
|
||
@blur="nameFocused = false"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 邮箱 -->
|
||
<view class="input-group">
|
||
<view class="input-container" :class="{ focused: emailFocused, filled: form.email }">
|
||
<view class="input-icon">
|
||
<svg viewBox="0 0 24 24" class="icon">
|
||
<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.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: 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"/>
|
||
</svg>
|
||
</view>
|
||
<input
|
||
class="input-field"
|
||
v-model.trim="form.password"
|
||
password
|
||
placeholder="请设置登录密码(至少6位)"
|
||
@focus="pwdFocused = true"
|
||
@blur="pwdFocused = false"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 发送验证码按钮 -->
|
||
<view class="input-group">
|
||
<button class="login-button" :disabled="countdown>0 || sending" @click="sendCode">{{ btnText }}</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 按钮区域 -->
|
||
<view class="actions-section">
|
||
<button class="register-button" @click="onRegister">
|
||
<text class="button-text">立即注册</text>
|
||
</button>
|
||
<button class="login-button" @click="onGoLogin">
|
||
<text class="button-text">已有账户?去登录</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 提示信息 -->
|
||
<view class="footer-section">
|
||
<text class="hint-text">注册即表示您同意我们的服务条款和隐私政策</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { post } from '../../common/http.js'
|
||
export default {
|
||
data() {
|
||
return {
|
||
form: {
|
||
shopName: '',
|
||
name: '',
|
||
email: '',
|
||
code: '',
|
||
password: ''
|
||
},
|
||
shopNameFocused: false,
|
||
nameFocused: 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 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 email = String(this.form.email||'').trim();
|
||
const name = String(this.form.name||'').trim();
|
||
try {
|
||
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.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)
|
||
}
|
||
} catch(e) {
|
||
const msg = (e && e.message) || '注册失败'
|
||
uni.showToast({ title: msg, icon: 'none' })
|
||
}
|
||
},
|
||
|
||
onGoLogin() {
|
||
uni.navigateTo({
|
||
url: '/pages/auth/login'
|
||
});
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss">
|
||
// 主容器
|
||
.register-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: 220rpx;
|
||
height: 220rpx;
|
||
top: 8%;
|
||
left: 12%;
|
||
animation: float 7s ease-in-out infinite;
|
||
}
|
||
|
||
&.circle-2 {
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
top: 65%;
|
||
right: 10%;
|
||
animation: float 9s ease-in-out infinite reverse;
|
||
}
|
||
|
||
&.circle-3 {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
bottom: 15%;
|
||
left: 25%;
|
||
animation: float 6s ease-in-out infinite;
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes float {
|
||
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||
50% { transform: translateY(-25px) rotate(5deg); }
|
||
}
|
||
|
||
// 主卡片容器
|
||
.register-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: 50rpx 40rpx 45rpx;
|
||
box-shadow: 0 25rpx 70rpx rgba(0, 0, 0, 0.12);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
// 头部区域
|
||
.header-section {
|
||
text-align: center;
|
||
margin-bottom: 45rpx;
|
||
|
||
.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: 24rpx;
|
||
|
||
.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;
|
||
|
||
.register-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;
|
||
}
|
||
}
|
||
|
||
.login-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 {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
color: #a0aec0;
|
||
line-height: 1.6;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.static-hint {
|
||
display: block;
|
||
font-size: 22rpx;
|
||
color: #a0aec0;
|
||
line-height: 1.5;
|
||
background: rgba(160, 174, 192, 0.1);
|
||
padding: 12rpx 16rpx;
|
||
border-radius: 10rpx;
|
||
border: 1rpx solid rgba(160, 174, 192, 0.2);
|
||
}
|
||
}
|
||
|
||
// 响应式设计
|
||
@media (max-width: 750rpx) {
|
||
.register-card {
|
||
margin: 20rpx;
|
||
padding: 40rpx 30rpx 35rpx;
|
||
}
|
||
|
||
.header-section .welcome-text {
|
||
font-size: 42rpx;
|
||
}
|
||
|
||
.form-section .input-group {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
}
|
||
</style>
|