完成一键登录和支付功能
This commit is contained in:
@@ -20,48 +20,42 @@
|
||||
<text class="phone-display">验证码登录</text>
|
||||
<text class="auth-tip">认证服务由中国移动提供</text>
|
||||
|
||||
<view class="form-group">
|
||||
<view class="input-wrapper" :class="{ focused: isSmsPhoneFocused }">
|
||||
<input
|
||||
v-model="smsPhone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="phone-input"
|
||||
confirm-type="next"
|
||||
@focus="isSmsPhoneFocused = true"
|
||||
@blur="isSmsPhoneFocused = false"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-group phone-form-group">
|
||||
<input
|
||||
v-model="smsPhone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="underline-input"
|
||||
confirm-type="next"
|
||||
@focus="isSmsPhoneFocused = true"
|
||||
@blur="isSmsPhoneFocused = false"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="form-group">
|
||||
<view class="input-wrapper code-input" :class="{ focused: isCodeFocused }">
|
||||
<input
|
||||
v-model="code"
|
||||
type="number"
|
||||
placeholder="请输入验证码"
|
||||
maxlength="6"
|
||||
class="phone-input"
|
||||
confirm-type="done"
|
||||
@focus="isCodeFocused = true"
|
||||
@blur="isCodeFocused = false"
|
||||
/>
|
||||
<button
|
||||
class="send-code-btn"
|
||||
:class="{ disabled: countdown > 0 || isLoading }"
|
||||
@tap="handleSendCode"
|
||||
:disabled="countdown > 0 || isLoading"
|
||||
>
|
||||
<text>{{ countdown > 0 ? `${countdown}秒后重发` : '发送验证码' }}</text>
|
||||
</button>
|
||||
</view>
|
||||
<view class="form-group code-form-group">
|
||||
<VerifyCodeInput
|
||||
ref="codeInputRef"
|
||||
v-model="code"
|
||||
:length="4"
|
||||
:mask="false"
|
||||
type="underline"
|
||||
activeColor="#FF8C42"
|
||||
@complete="handleCodeComplete"
|
||||
@focus="onInputFocus"
|
||||
@blur="onInputBlur"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<button class="login-btn primary" :class="{ disabled: !canSmsSubmit, loading: isLoading }" @tap="handleSmsLogin" :disabled="!canSmsSubmit || isLoading">
|
||||
<text>{{ isLoading ? '登录中...' : '验证码登录' }}</text>
|
||||
|
||||
<button
|
||||
class="login-btn primary"
|
||||
:class="{ disabled: countdown > 0 || isLoading }"
|
||||
@tap="handleSendCode"
|
||||
:disabled="countdown > 0 || isLoading"
|
||||
>
|
||||
<text>{{ countdown > 0 ? `${countdown}秒后重发` : '发送验证码' }}</text>
|
||||
</button>
|
||||
|
||||
|
||||
<button class="login-btn secondary" @tap="switchToPhone">
|
||||
<text>其他登录方式</text>
|
||||
</button>
|
||||
@@ -84,22 +78,7 @@
|
||||
<text class="phone-display">本机号码登录</text>
|
||||
<text class="auth-tip">认证服务由中国移动提供</text>
|
||||
|
||||
<view class="form-group phone-login">
|
||||
<view class="input-wrapper" :class="{ focused: isInputFocused }">
|
||||
<input
|
||||
v-model="phone"
|
||||
type="number"
|
||||
placeholder="请输入手机号"
|
||||
maxlength="11"
|
||||
class="phone-input"
|
||||
confirm-type="done"
|
||||
@focus="isInputFocused = true"
|
||||
@blur="isInputFocused = false"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="login-btn primary" :class="{ disabled: !canSubmit, loading: isLoading }" @tap="handlePhoneLogin" :disabled="!canSubmit || isLoading">
|
||||
<button class="login-btn primary" :class="{ disabled: isLoading, loading: isLoading }" @tap="handlePhoneLogin" :disabled="isLoading">
|
||||
<text>{{ isLoading ? '登录中...' : '一键登录' }}</text>
|
||||
</button>
|
||||
|
||||
@@ -123,17 +102,14 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { setToken } from '@/utils/request.js'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { setToken, setRefreshToken } from '@/utils/request.js'
|
||||
import { loginWithPhone, oneClickLogin, sendCode } from '@/api/main.js'
|
||||
|
||||
// 调用真实后端API,不再使用测试数据
|
||||
import VerifyCodeInput from '@/components/global/VerifyCodeInput.vue'
|
||||
|
||||
const activeCard = ref('phone')
|
||||
const phone = ref('')
|
||||
const isLoading = ref(false)
|
||||
const isInputFocused = ref(false)
|
||||
const agreed = ref(false)
|
||||
const agreed = ref(true)
|
||||
const shakeAgreement = ref(false)
|
||||
|
||||
const smsPhone = ref('')
|
||||
@@ -157,13 +133,27 @@ function showToast(msg) {
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(phone.value) && !isLoading.value
|
||||
})
|
||||
|
||||
const canSmsSubmit = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(smsPhone.value) && /^\d{6}$/.test(code.value) && !isLoading.value
|
||||
})
|
||||
/**
|
||||
* 保存登录信息到本地存储
|
||||
*/
|
||||
function saveLoginInfo(loginInfo) {
|
||||
if (!loginInfo) return
|
||||
|
||||
// 保存 token (已经在 request.js 中通过 setToken 保存)
|
||||
|
||||
// 保存会员信息
|
||||
if (loginInfo.memberId || loginInfo.id) {
|
||||
const memberInfo = {
|
||||
id: loginInfo.memberId || loginInfo.id,
|
||||
memberId: loginInfo.memberId || loginInfo.id,
|
||||
phone: loginInfo.phone || '',
|
||||
nickname: loginInfo.nickname || '',
|
||||
avatar: loginInfo.avatar || ''
|
||||
}
|
||||
uni.setStorageSync('loginMemberInfo', memberInfo)
|
||||
console.log('[login] 登录信息已保存:', memberInfo)
|
||||
}
|
||||
}
|
||||
|
||||
function onAgreementChange(e) {
|
||||
agreed.value = e.detail.value.includes('agreed')
|
||||
@@ -175,8 +165,6 @@ function switchToSms() {
|
||||
|
||||
function switchToPhone() {
|
||||
activeCard.value = 'phone'
|
||||
phone.value = ''
|
||||
phoneError.value = ''
|
||||
}
|
||||
|
||||
function showAgreement() {
|
||||
@@ -187,16 +175,20 @@ function showPrivacy() {
|
||||
uni.showModal({ title: '隐私政策', content: '这里是隐私政策内容...', showCancel: false })
|
||||
}
|
||||
|
||||
function validatePhone() {
|
||||
if (!phone.value) {
|
||||
showToast('请输入手机号')
|
||||
return false
|
||||
function handleCodeComplete(val) {
|
||||
if (!isLoading.value) {
|
||||
setTimeout(() => {
|
||||
handleSmsLogin()
|
||||
}, 100)
|
||||
}
|
||||
if (!/^1[3-9]\d{9}$/.test(phone.value)) {
|
||||
showToast('请输入正确的手机号')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function onInputFocus() {
|
||||
isCodeFocused.value = true
|
||||
}
|
||||
|
||||
function onInputBlur() {
|
||||
isCodeFocused.value = false
|
||||
}
|
||||
|
||||
function validateSmsPhone() {
|
||||
@@ -217,34 +209,146 @@ async function handlePhoneLogin() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!validatePhone()) return
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const res = await oneClickLogin({
|
||||
accessToken: phone.value,
|
||||
openid: 'uniapp_phone_login',
|
||||
nickname: '',
|
||||
avatar: ''
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.preLogin({
|
||||
provider: 'univerify',
|
||||
success: resolve,
|
||||
fail: (err) => {
|
||||
console.warn('预登录失败,切换到验证码登录:', err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (res && res.accessToken) {
|
||||
setToken(res.accessToken)
|
||||
uni.setStorageSync('memberInfo', res)
|
||||
uni.setStorageSync('isLogin', true)
|
||||
showToast('登录成功')
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/memberInfo/memberInfo' })
|
||||
}, 1500)
|
||||
} else {
|
||||
showToast('登录失败,请重试')
|
||||
}
|
||||
uni.login({
|
||||
provider: 'univerify',
|
||||
loginBtnText: '本机号码一键登录',
|
||||
univerifyStyle: {
|
||||
fullScreen: true,
|
||||
backgroundColor: '#ffffff',
|
||||
icon: {
|
||||
path: '/static/logo.png',
|
||||
width: '60px',
|
||||
height: '60px'
|
||||
},
|
||||
phoneNum: {
|
||||
color: '#333333'
|
||||
},
|
||||
slogan: {
|
||||
color: '#999999'
|
||||
},
|
||||
authButton: {
|
||||
normalColor: '#FF8C42',
|
||||
highlightColor: '#E07A38',
|
||||
disabledColor: '#FFB88A',
|
||||
textColor: '#ffffff',
|
||||
title: '本机号码一键登录',
|
||||
borderRadius: '24px'
|
||||
},
|
||||
otherLoginButton: {
|
||||
visible: true,
|
||||
normalColor: '#ffffff',
|
||||
highlightColor: '#f5f5f5',
|
||||
textColor: '#666666',
|
||||
title: '其他登录方式',
|
||||
borderColor: '#e5e5e5',
|
||||
borderRadius: '24px'
|
||||
},
|
||||
privacyTerms: {
|
||||
defaultCheckBoxState: true,
|
||||
textColor: '#999999',
|
||||
termsColor: '#FF8C42',
|
||||
prefix: '我已阅读并同意',
|
||||
suffix: '并使用本机号码登录',
|
||||
privacyItems: [
|
||||
{
|
||||
url: 'https://example.com/agreement',
|
||||
title: '用户服务协议'
|
||||
},
|
||||
{
|
||||
url: 'https://example.com/privacy',
|
||||
title: '隐私政策'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
success: async (loginRes) => {
|
||||
uni.closeAuthView()
|
||||
|
||||
try {
|
||||
const authResult = loginRes.authResult
|
||||
if (!authResult) {
|
||||
console.error('authResult为空:', loginRes)
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const accessToken = authResult.accessToken || authResult.access_token || authResult.token
|
||||
const openid = authResult.openid || authResult.openId
|
||||
|
||||
console.log('accessToken:', accessToken, 'openid:', openid)
|
||||
|
||||
if (!accessToken) {
|
||||
console.error('accessToken为空,authResult:', authResult)
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const res = await oneClickLogin({
|
||||
accessToken: accessToken,
|
||||
openid: openid,
|
||||
nickname: '',
|
||||
avatar: ''
|
||||
})
|
||||
|
||||
if (res && res.accessToken) {
|
||||
setToken(res.accessToken)
|
||||
if (res.refreshToken) {
|
||||
setRefreshToken(res.refreshToken)
|
||||
}
|
||||
saveLoginInfo(res)
|
||||
showToast('登录成功')
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}, 1500)
|
||||
} else {
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('一键登录处理失败:', e)
|
||||
showToast(e?.message || '一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.closeAuthView()
|
||||
|
||||
console.error('一键登录失败:', err)
|
||||
isLoading.value = false
|
||||
|
||||
if (err.code === 30008 || err.errMsg && err.errMsg.includes('其他登录方式')) {
|
||||
switchToSms()
|
||||
} else {
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
switchToSms()
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('一键登录失败:', err)
|
||||
showToast(err?.message || '登录失败,请重试')
|
||||
} finally {
|
||||
console.error('一键登录调用失败:', err)
|
||||
showToast('一键登录失败,请使用验证码登录')
|
||||
isLoading.value = false
|
||||
switchToSms()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,17 +404,13 @@ async function handleSmsLogin() {
|
||||
triggerAgreementShake()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (!validateSmsPhone()) return
|
||||
if (!code.value) {
|
||||
showToast('请输入验证码')
|
||||
if (code.value.length !== 4) {
|
||||
showToast('请输入4位验证码')
|
||||
return
|
||||
}
|
||||
if (!/^\d{6}$/.test(code.value)) {
|
||||
showToast('请输入6位验证码')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
@@ -321,8 +421,10 @@ async function handleSmsLogin() {
|
||||
|
||||
if (res && res.accessToken) {
|
||||
setToken(res.accessToken)
|
||||
uni.setStorageSync('memberInfo', res)
|
||||
uni.setStorageSync('isLogin', true)
|
||||
if (res.refreshToken) {
|
||||
setRefreshToken(res.refreshToken)
|
||||
}
|
||||
saveLoginInfo(res)
|
||||
showToast('登录成功')
|
||||
setTimeout(() => {
|
||||
uni.switchTab({ url: '/pages/memberInfo/memberInfo' })
|
||||
@@ -418,12 +520,12 @@ button::after {
|
||||
.card-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 620rpx;
|
||||
height: 500rpx;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
position: absolute;
|
||||
height: 700rpx;
|
||||
height: 620rpx;
|
||||
background: $bg-white;
|
||||
border-radius: $radius-lg;
|
||||
padding: 48rpx 40rpx;
|
||||
@@ -431,13 +533,19 @@ button::after {
|
||||
will-change: transform, z-index, filter;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 560rpx;
|
||||
}
|
||||
|
||||
.sms-card {
|
||||
z-index: 1;
|
||||
top: -60rpx;
|
||||
left: -100rpx;
|
||||
box-shadow: 0 16rpx 40rpx rgba(0, 0, 0, 0.12);
|
||||
filter: blur(10rpx);
|
||||
|
||||
|
||||
&.active {
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
@@ -453,7 +561,7 @@ button::after {
|
||||
left: 0;
|
||||
box-shadow: 0 24rpx 60rpx rgba(0, 0, 0, 0.18), 0 8rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
filter: blur(0);
|
||||
|
||||
|
||||
&:not(.active) {
|
||||
z-index: 1;
|
||||
top: -60rpx;
|
||||
@@ -485,45 +593,45 @@ button::after {
|
||||
.form-group {
|
||||
width: 100%;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phone-login{
|
||||
margin-bottom: 150rpx;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
.phone-form-group {
|
||||
height: 100rpx;
|
||||
width: 360rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
padding: 0 32rpx;
|
||||
height: 100rpx;
|
||||
border-bottom: 2rpx solid $border-light;
|
||||
|
||||
&.code-input {
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.phone-input {
|
||||
flex: 1;
|
||||
font-size: 34rpx;
|
||||
.underline-input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
color: $text-dark;
|
||||
letter-spacing: 2rpx;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2rpx solid $border-light;
|
||||
padding: 0 20rpx;
|
||||
transition: border-color 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
border-bottom-color: $accent-orange;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: $text-light;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.send-code-btn {
|
||||
width: 200rpx;
|
||||
height: 72rpx;
|
||||
background: $accent-orange;
|
||||
color: $text-inverse;
|
||||
font-size: 26rpx;
|
||||
border-radius: $radius-sm;
|
||||
border: none;
|
||||
|
||||
&.disabled {
|
||||
background: $border-light;
|
||||
color: $text-light;
|
||||
}
|
||||
.code-form-group {
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
|
||||
Reference in New Issue
Block a user