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

541 lines
18 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="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>