This commit is contained in:
2025-09-24 20:35:15 +08:00
parent 39679f7330
commit 8a458ff0a4
12033 changed files with 1537546 additions and 13292 deletions

View 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>