Files
PartsInquiry/frontend/pages/my/sms-login.vue
2025-09-24 20:35:15 +08:00

145 lines
6.4 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="page sms-login">
<view class="card">
<view class="title">短信验证码登录</view>
<view class="form">
<input class="input" type="number" maxlength="11" placeholder="请输入手机号" v-model="phone"/>
<view class="row">
<input class="input code" type="number" maxlength="6" placeholder="请输入验证码" v-model="code"/>
<button class="send" :disabled="countdown>0 || sending" @click="sendCode">{{ btnText }}</button>
</view>
<button class="login" type="primary" :disabled="logging" @click="doLogin">登录/注册</button>
<button class="login" :disabled="logging" @click="quickRegister">注册为店主</button>
</view>
<view class="hint">首次登录将自动创建店铺与店主用户</view>
<view class="debug">
<view class="debug-title" @click="toggleDebug">请求体示例点击{{ showDebug? '收起':'展开' }}</view>
<view v-if="showDebug" class="debug-body">
<view class="code-title">POST /api/auth/sms/send</view>
<view class="code-wrap">
<text class="code">{{ sendBodyJson }}</text>
<button size="mini" class="copy" @click="copy(sendBodyJson)">复制</button>
</view>
<view class="code-title">POST /api/auth/sms/login</view>
<view class="code-wrap">
<text class="code">{{ loginBodyJson }}</text>
<button size="mini" class="copy" @click="copy(loginBodyJson)">复制</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { post } from '../../common/http.js'
export default {
data(){
return { phone: '', code: '', countdown: 0, timer: null, sending: false, logging: false, showDebug: true }
},
computed:{
btnText(){ return this.countdown>0 ? `${this.countdown}s` : (this.sending ? '发送中...' : '获取验证码') }
,
trimmedPhone(){ return String(this.phone||'').trim() },
sendBodyJson(){ return JSON.stringify({ phone: this.trimmedPhone, scene: 'login' }, null, 2) },
loginBodyJson(){ return JSON.stringify({ phone: this.trimmedPhone, code: String(this.code||'').trim() }, null, 2) }
},
onUnload(){ if (this.timer) clearInterval(this.timer) },
methods:{
validatePhone(p){ return /^1\d{10}$/.test(String(p||'').trim()) },
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 p = String(this.phone||'').trim()
if (!this.validatePhone(p)) return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
this.sending = true
try {
const res = await post('/api/auth/sms/send', { phone: p, 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 doLogin(){
if (this.logging) return
const p = String(this.phone||'').trim()
const c = String(this.code||'').trim()
if (!this.validatePhone(p)) return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
if (!/^\d{6}$/.test(c)) return uni.showToast({ title: '验证码格式不正确', icon: 'none' })
this.logging = true
try {
const data = await post('/api/auth/sms/login', { phone: p, code: c })
if (data && data.token) {
uni.setStorageSync('TOKEN', data.token)
if (data.user && data.user.phone) uni.setStorageSync('USER_MOBILE', data.user.phone)
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' })
} finally { this.logging=false }
}
,
async quickRegister(){
if (this.logging) return
const p = String(this.phone||'').trim()
if (!this.validatePhone(p)) return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
this.logging = true
try {
const data = await post('/api/auth/register', { phone: p })
if (data && data.token) {
uni.setStorageSync('TOKEN', data.token)
if (data.user && data.user.phone) uni.setStorageSync('USER_MOBILE', data.user.phone)
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' })
} finally { this.logging=false }
}
,
copy(text){
try { uni.setClipboardData({ data: String(text||'') }); uni.showToast({ title: '已复制', icon: 'none' }) } catch(e) {}
}
,
toggleDebug(){ this.showDebug = !this.showDebug }
}
}
</script>
<style lang="scss">
.sms-login { padding: 24rpx; }
.card { background: $uni-bg-color-grey; border-radius: 16rpx; padding: 28rpx; }
.title { font-size: 32rpx; font-weight: 700; margin-bottom: 20rpx; }
.form { display: flex; flex-direction: column; gap: 16rpx; }
.row { display: flex; gap: 12rpx; }
.input { background: #fff; border: 1rpx solid $uni-border-color; border-radius: 12rpx; padding: 20rpx; font-size: 28rpx; flex: 1; }
.input.code { flex: 1; }
.send { min-width: 220rpx; }
.login { margin-top: 8rpx; }
.hint { margin-top: 12rpx; font-size: 22rpx; color: $uni-text-color-grey; }
.debug { margin-top: 20rpx; }
.debug-title { font-size: 26rpx; color: $uni-text-color-grey; }
.debug-body { margin-top: 12rpx; display: flex; flex-direction: column; gap: 12rpx; }
.code-title { font-size: 24rpx; color: $uni-text-color-grey; }
.code-wrap { position: relative; background: #fff; border: 1rpx solid $uni-border-color; border-radius: 12rpx; padding: 16rpx; }
.code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 24rpx; white-space: pre-wrap; }
.copy { position: absolute; top: 8rpx; right: 8rpx; }
</style>