1
This commit is contained in:
144
frontend/pages/my/sms-login.vue
Normal file
144
frontend/pages/my/sms-login.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user