412 lines
9.2 KiB
Vue
412 lines
9.2 KiB
Vue
<template>
|
||
<view class="phone-auth-container page-container-with-bg">
|
||
<!-- 顶部说明卡片 -->
|
||
<view class="auth-card">
|
||
<view class="auth-header">
|
||
<view class="auth-icon">
|
||
<text class="icon-text">📱</text>
|
||
</view>
|
||
<text class="auth-title">手机号授权</text>
|
||
<text class="auth-subtitle">为了更好地为您提供服务,需要获取您的手机号</text>
|
||
</view>
|
||
|
||
<view class="auth-benefits">
|
||
<view class="benefit-item">
|
||
<text class="benefit-icon">🔒</text>
|
||
<text class="benefit-text">保护账号安全</text>
|
||
</view>
|
||
<view class="benefit-item">
|
||
<text class="benefit-icon">📞</text>
|
||
<text class="benefit-text">重要消息通知</text>
|
||
</view>
|
||
<view class="benefit-item">
|
||
<text class="benefit-icon">🎯</text>
|
||
<text class="benefit-text">个性化服务</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 授权按钮卡片 -->
|
||
<view class="button-card">
|
||
<button
|
||
class="auth-button"
|
||
:class="{ 'loading': authorizing }"
|
||
open-type="getPhoneNumber"
|
||
@getphonenumber="onGetPhoneNumber"
|
||
:disabled="authorizing"
|
||
>
|
||
<view class="button-content">
|
||
<view v-if="authorizing" class="loading-spinner"></view>
|
||
<text v-if="!authorizing">授权手机号</text>
|
||
<text v-else>授权中...</text>
|
||
</view>
|
||
</button>
|
||
|
||
<view class="auth-tips">
|
||
<text class="tips-text">点击授权后,我们将获取您的手机号用于账号验证</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 隐私说明 -->
|
||
<view class="privacy-card">
|
||
<text class="privacy-title">隐私保护</text>
|
||
<text class="privacy-text">我们承诺严格保护您的个人信息,仅用于账号安全验证和必要的服务通知,不会用于其他商业用途。</text>
|
||
</view>
|
||
|
||
<!-- 跳过按钮 -->
|
||
<view class="skip-section">
|
||
<text class="skip-button" @click="skipAuth">暂时跳过</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { ref } from 'vue'
|
||
import { wxPhoneLogin } from '@/http/api/auth.js'
|
||
import {
|
||
savePhoneAuthData,
|
||
markPhoneSkipped,
|
||
STORAGE_KEYS
|
||
} from '@/utils/loginState.js'
|
||
|
||
export default {
|
||
name: 'PhoneAuthPage',
|
||
setup() {
|
||
// 响应式数据
|
||
const authorizing = ref(false)
|
||
|
||
// 防止重复点击
|
||
let isProcessing = false
|
||
|
||
// 错误处理函数
|
||
const handleError = (error) => {
|
||
let errorMessage = '授权失败,请重试'
|
||
let shouldReturnToLogin = false
|
||
|
||
console.error('手机号授权错误详情:', error)
|
||
|
||
if (error.message) {
|
||
if (error.message.includes('网络')) {
|
||
errorMessage = '网络连接异常,请检查网络后重试'
|
||
} else if (error.message.includes('登录状态已过期') || error.message.includes('重新获取微信登录code失败')) {
|
||
errorMessage = '登录状态已过期,请重新登录'
|
||
shouldReturnToLogin = true
|
||
} else if (error.message.includes('API返回数据格式错误')) {
|
||
errorMessage = '服务器响应异常,请稍后重试'
|
||
} else if (error.message.includes('用户拒绝')) {
|
||
errorMessage = '需要授权手机号才能继续使用'
|
||
} else if (error.code === 401) {
|
||
errorMessage = '授权已过期,请重新登录'
|
||
shouldReturnToLogin = true
|
||
} else if (error.code === 500) {
|
||
errorMessage = '服务器错误,请稍后重试'
|
||
} else {
|
||
errorMessage = error.message || '授权失败,请重试'
|
||
}
|
||
}
|
||
|
||
uni.showToast({
|
||
title: errorMessage,
|
||
icon: 'none',
|
||
duration: 3000
|
||
})
|
||
|
||
// 如果需要返回登录页面
|
||
if (shouldReturnToLogin) {
|
||
setTimeout(() => {
|
||
uni.reLaunch({
|
||
url: '/pages/profile/profile'
|
||
})
|
||
}, 3000)
|
||
}
|
||
}
|
||
|
||
// 方法定义
|
||
const onGetPhoneNumber = async (e) => {
|
||
console.log('获取手机号回调:', e)
|
||
|
||
// 防止重复点击
|
||
if (isProcessing) {
|
||
console.log('正在处理中,忽略重复点击')
|
||
return
|
||
}
|
||
|
||
if (e.detail.errMsg === 'getPhoneNumber:ok') {
|
||
isProcessing = true
|
||
authorizing.value = true
|
||
|
||
try {
|
||
// 获取微信登录code
|
||
let loginCode = uni.getStorageSync(STORAGE_KEYS.WX_LOGIN_CODE)
|
||
|
||
// 检查登录code是否存在和有效
|
||
if (!loginCode) {
|
||
console.warn('微信登录code不存在,尝试重新获取')
|
||
// 尝试重新获取微信登录code
|
||
try {
|
||
const loginRes = await new Promise((resolve, reject) => {
|
||
uni.login({
|
||
success: resolve,
|
||
fail: reject
|
||
})
|
||
})
|
||
|
||
if (loginRes.code) {
|
||
loginCode = loginRes.code
|
||
uni.setStorageSync(STORAGE_KEYS.WX_LOGIN_CODE, loginCode)
|
||
console.log('重新获取微信登录code成功')
|
||
} else {
|
||
throw new Error('重新获取微信登录code失败')
|
||
}
|
||
} catch (loginError) {
|
||
console.error('重新获取微信登录code失败:', loginError)
|
||
throw new Error('登录状态已过期,请重新登录')
|
||
}
|
||
}
|
||
|
||
// 调用后端API进行手机号登录
|
||
const phoneData = {
|
||
code: loginCode,
|
||
encryptedData: e.detail.encryptedData,
|
||
iv: e.detail.iv
|
||
}
|
||
|
||
// 调用手机号登录API
|
||
const result = await wxPhoneLogin(phoneData, {
|
||
custom: {
|
||
loading: false, // 使用页面自己的loading状态
|
||
catch: true // 确保错误能被catch到
|
||
}
|
||
})
|
||
|
||
// 处理登录结果
|
||
if (result && result.token) {
|
||
// 保存手机号登录数据
|
||
savePhoneAuthData({
|
||
token: result.token,
|
||
refreshToken: result.refreshToken,
|
||
userInfo: result.userInfo || result
|
||
})
|
||
|
||
uni.showToast({
|
||
title: '手机号授权成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
})
|
||
|
||
// 跳转到用户信息设置页面
|
||
setTimeout(() => {
|
||
uni.navigateTo({
|
||
url: '/pages/profile/user-info?mode=setup'
|
||
})
|
||
}, 1500)
|
||
} else {
|
||
throw new Error('手机号登录失败')
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('手机号授权失败:', error)
|
||
handleError(error)
|
||
} finally {
|
||
authorizing.value = false
|
||
isProcessing = false
|
||
}
|
||
} else {
|
||
// 用户拒绝授权
|
||
uni.showToast({
|
||
title: '需要授权手机号才能继续',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
const skipAuth = () => {
|
||
uni.showModal({
|
||
title: '确认跳过',
|
||
content: '跳过手机号授权可能会影响部分功能的使用,确定要跳过吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 使用状态管理工具标记跳过状态
|
||
markPhoneSkipped()
|
||
|
||
// 跳转到个人信息设置页面
|
||
uni.navigateTo({
|
||
url: '/pages/profile/user-info?mode=setup&phoneSkipped=true'
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
return {
|
||
authorizing,
|
||
onGetPhoneNumber,
|
||
skipAuth
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.phone-auth-container {
|
||
background: linear-gradient(135deg, #FF8A80 0%, #FFB6C1 25%, #FECFEF 50%, #F8BBD9 100%);
|
||
min-height: 100vh;
|
||
padding: 40rpx 30rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 通用卡片样式 */
|
||
.auth-card,
|
||
.button-card,
|
||
.privacy-card {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(20rpx);
|
||
border-radius: 24rpx;
|
||
padding: 32rpx;
|
||
margin-bottom: 24rpx;
|
||
box-shadow: 0 8rpx 32rpx rgba(255, 138, 128, 0.2);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
/* 授权说明卡片 */
|
||
.auth-header {
|
||
text-align: center;
|
||
margin-bottom: 32rpx;
|
||
|
||
.auth-icon {
|
||
margin-bottom: 16rpx;
|
||
|
||
.icon-text {
|
||
font-size: 80rpx;
|
||
}
|
||
}
|
||
|
||
.auth-title {
|
||
display: block;
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.auth-subtitle {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
line-height: 1.5;
|
||
}
|
||
}
|
||
|
||
.auth-benefits {
|
||
.benefit-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.benefit-icon {
|
||
font-size: 32rpx;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.benefit-text {
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 按钮卡片 */
|
||
.button-card {
|
||
text-align: center;
|
||
|
||
.auth-button {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background: linear-gradient(135deg, #FF8A80, #FFB6C1);
|
||
border-radius: 24rpx;
|
||
border: none;
|
||
color: white;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 16rpx;
|
||
transition: all 0.3s ease;
|
||
|
||
&.loading {
|
||
opacity: 0.8;
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
&:disabled {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.button-content {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||
border-top: 4rpx solid white;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.auth-tips {
|
||
.tips-text {
|
||
font-size: 24rpx;
|
||
color: #999999;
|
||
line-height: 1.4;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 隐私说明卡片 */
|
||
.privacy-card {
|
||
.privacy-title {
|
||
display: block;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.privacy-text {
|
||
font-size: 24rpx;
|
||
color: #666666;
|
||
line-height: 1.6;
|
||
}
|
||
}
|
||
|
||
/* 跳过按钮 */
|
||
.skip-section {
|
||
margin-top: auto;
|
||
text-align: center;
|
||
padding: 20rpx 0;
|
||
|
||
.skip-button {
|
||
font-size: 28rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
text-decoration: underline;
|
||
}
|
||
}
|
||
</style>
|