593 lines
18 KiB
Vue
593 lines
18 KiB
Vue
<template>
|
||
<view class="sms-verify-page">
|
||
<!-- 顶部导航栏 -->
|
||
<view class="nav-bar">
|
||
<view class="nav-back" @click="goBack">
|
||
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
|
||
</view>
|
||
<view class="nav-title">短信验证</view>
|
||
<view class="nav-placeholder"></view>
|
||
</view>
|
||
|
||
<!-- 页面内容 -->
|
||
<view class="page-content">
|
||
<!-- 安全提示 -->
|
||
<view class="security-tip">
|
||
<view class="tip-icon">
|
||
<uni-icons type="auth-filled" size="32" color="#256BFA"></uni-icons>
|
||
</view>
|
||
<view class="tip-text">为了您的账户安全需要您进行验证</view>
|
||
</view>
|
||
|
||
<!-- 手机号显示 -->
|
||
<view class="phone-display">
|
||
<view class="phone-label">验证码将发送至</view>
|
||
<view class="phone-number">{{ formattedPhone }}</view>
|
||
</view>
|
||
|
||
<!-- 验证码输入区域 -->
|
||
<view class="sms-input-area">
|
||
<view class="input-label">请输入6位数字验证码</view>
|
||
<view class="single-input-container">
|
||
<input
|
||
ref="smsInput"
|
||
class="single-input"
|
||
type="tel"
|
||
inputmode="numeric"
|
||
pattern="[0-9]*"
|
||
autocomplete="one-time-code"
|
||
maxlength="6"
|
||
:value="smsCode"
|
||
@input="onCodeInput"
|
||
@paste="onPaste"
|
||
placeholder="请输入验证码"
|
||
:focus="autoFocus"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 倒计时和重发 -->
|
||
<view class="countdown-section">
|
||
<view v-if="countdown > 0" class="countdown-text">
|
||
{{ countdown }}秒后重新获取
|
||
</view>
|
||
<view v-else class="resend-text" @click="resendSms">
|
||
重新获取验证码
|
||
</view>
|
||
<view class="paste-section">
|
||
<view class="paste-text" @click="pasteFromClipboard">
|
||
<uni-icons type="clipboard" size="16" color="#256BFA"></uni-icons>
|
||
<text>粘贴验证码</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 提交按钮 -->
|
||
<button class="submit-btn" :disabled="!canSubmit" @click="submitVerification">
|
||
<text v-if="!loading">确认</text>
|
||
<view v-else class="loading-spinner"></view>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, inject, computed, onMounted, onUnmounted } from 'vue';
|
||
import { onLoad } from '@dcloudio/uni-app';
|
||
|
||
const { $api } = inject('globalFunction');
|
||
|
||
// 从页面参数获取数据
|
||
const phone = ref('');
|
||
const openid = ref('');
|
||
const unionid = ref('');
|
||
const userType = ref('');
|
||
const orgType = ref('');
|
||
|
||
// 验证码相关
|
||
const smsCode = ref(''); // 单个字符串验证码
|
||
const smsInput = ref(null); // 输入框引用
|
||
const countdown = ref(60); // 倒计时
|
||
const timer = ref(null);
|
||
const loading = ref(false);
|
||
const autoFocus = ref(true); // 自动聚焦
|
||
|
||
// 格式化手机号:只显示前三位和后四位,中间用星号代替
|
||
const formattedPhone = computed(() => {
|
||
console.log('formattedPhone computed called, phone.value:', phone.value);
|
||
|
||
if (!phone.value || phone.value.trim() === '') {
|
||
console.log('手机号为空,显示未知号码');
|
||
return '未知号码';
|
||
}
|
||
|
||
// 清理手机号,移除空格和特殊字符
|
||
const cleanPhone = phone.value.replace(/\D/g, '');
|
||
|
||
if (cleanPhone.length < 11) {
|
||
console.log('手机号长度不足11位,显示原始值:', phone.value);
|
||
return phone.value;
|
||
}
|
||
|
||
const prefix = cleanPhone.substring(0, 3);
|
||
const suffix = cleanPhone.substring(cleanPhone.length - 4);
|
||
const formatted = `${prefix}****${suffix}`;
|
||
|
||
console.log('格式化后的手机号:', formatted);
|
||
return formatted;
|
||
});
|
||
|
||
// 是否可以提交
|
||
const canSubmit = computed(() => {
|
||
const can = smsCode.value.length === 6 && !loading.value;
|
||
console.log('canSubmit计算属性调用:', {
|
||
smsCodeLength: smsCode.value.length,
|
||
loading: loading.value,
|
||
canSubmit: can
|
||
});
|
||
return can;
|
||
});
|
||
|
||
// 页面加载时获取参数
|
||
onLoad(async (options) => {
|
||
console.log('短信验证页面参数:', options);
|
||
console.log('所有参数键值对:');
|
||
Object.keys(options).forEach(key => {
|
||
console.log(` ${key}: ${options[key]}`);
|
||
});
|
||
|
||
// 尝试多种可能的手机号字段名(按优先级)
|
||
const possiblePhoneFields = [
|
||
'phone', 'mobile', 'phoneNumber', 'tel',
|
||
'phoneNum', 'userPhone', 'telephone', 'mobilePhone', 'cellphone',
|
||
'userTel', 'userMobile', 'contactPhone'
|
||
];
|
||
|
||
let foundPhone = '';
|
||
for (const field of possiblePhoneFields) {
|
||
if (options[field]) {
|
||
foundPhone = options[field];
|
||
console.log(`找到手机号字段 "${field}": ${foundPhone}`);
|
||
break;
|
||
}
|
||
}
|
||
|
||
// 如果没找到,尝试从URL参数中解析
|
||
if (!foundPhone) {
|
||
console.log('未在options中找到手机号字段,尝试从当前页面URL解析');
|
||
const currentPages = getCurrentPages();
|
||
if (currentPages.length > 0) {
|
||
const currentPage = currentPages[currentPages.length - 1];
|
||
console.log('当前页面对象:', currentPage);
|
||
|
||
// 尝试从页面路由参数中获取
|
||
if (currentPage.$page && currentPage.$page.fullPath) {
|
||
const urlParams = new URLSearchParams(currentPage.$page.fullPath.split('?')[1] || '');
|
||
for (const field of possiblePhoneFields) {
|
||
const value = urlParams.get(field);
|
||
if (value) {
|
||
foundPhone = value;
|
||
console.log(`从URL参数中找到手机号字段 "${field}": ${foundPhone}`);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
phone.value = foundPhone || '';
|
||
openid.value = options.openid || '';
|
||
unionid.value = options.unionid || '';
|
||
userType.value = options.userType || '';
|
||
orgType.value = options.orgType || '';
|
||
|
||
console.log('最终获取到的手机号:', phone.value);
|
||
console.log('手机号长度:', phone.value.length);
|
||
console.log('formattedPhone值:', formattedPhone.value);
|
||
|
||
// 检查所有可能包含手机号的字段
|
||
console.log('所有可能包含手机号的字段值:');
|
||
possiblePhoneFields.forEach(field => {
|
||
if (options[field]) {
|
||
console.log(` ${field}: ${options[field]}`);
|
||
}
|
||
});
|
||
|
||
// 如果手机号仍然为空,显示错误信息
|
||
if (!phone.value) {
|
||
console.error('无法获取手机号,请检查参数传递');
|
||
uni.showToast({ title: '无法获取手机号,请返回重试', icon: 'none' });
|
||
}
|
||
|
||
// 开始倒计时
|
||
startCountdown();
|
||
|
||
// 自动聚焦到输入框
|
||
setTimeout(() => {
|
||
if (smsInput.value && typeof smsInput.value.focus === 'function') {
|
||
smsInput.value.focus();
|
||
}
|
||
}, 300);
|
||
});
|
||
|
||
// 开始倒计时
|
||
const startCountdown = () => {
|
||
clearInterval(timer.value);
|
||
countdown.value = 60;
|
||
timer.value = setInterval(() => {
|
||
if (countdown.value > 0) {
|
||
countdown.value--;
|
||
} else {
|
||
clearInterval(timer.value);
|
||
}
|
||
}, 1000);
|
||
};
|
||
|
||
// 重发验证码
|
||
const resendSms = async () => {
|
||
if (countdown.value > 0) return;
|
||
|
||
// 调用重新发送验证码接口(如果需要)
|
||
uni.showLoading({ title: '发送中...' });
|
||
try {
|
||
// 假设有一个重新发送验证码的接口
|
||
// await $api.createRequest('/app/resendSms', { phone: phone.value }, 'post');
|
||
uni.hideLoading();
|
||
uni.showToast({ title: '验证码已发送', icon: 'success' });
|
||
startCountdown();
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: error.msg || '发送失败', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
// 验证码输入处理
|
||
const onCodeInput = (e) => {
|
||
let value = e.detail.value.replace(/\D/g, ''); // 只保留数字
|
||
|
||
// 限制为6位数字
|
||
if (value.length > 6) {
|
||
value = value.substring(0, 6);
|
||
}
|
||
|
||
smsCode.value = value;
|
||
|
||
// 如果输入了6位数字,可以自动提交(可选)
|
||
if (value.length === 6) {
|
||
// 这里可以添加自动提交逻辑,或者让用户手动点击按钮
|
||
console.log('6位验证码已输入完整');
|
||
}
|
||
};
|
||
|
||
// 粘贴事件处理
|
||
const onPaste = (e) => {
|
||
console.log('onPaste event triggered', e);
|
||
|
||
// #ifdef MP-WEIXIN
|
||
// 微信小程序中,使用异步方式获取剪贴板
|
||
console.log('MP-WEIXIN: onPaste called, using async clipboard API');
|
||
setTimeout(() => {
|
||
pasteFromClipboard();
|
||
}, 50);
|
||
|
||
// 尝试阻止默认行为
|
||
if (e && e.preventDefault) {
|
||
e.preventDefault();
|
||
}
|
||
if (e && e.stopPropagation) {
|
||
e.stopPropagation();
|
||
}
|
||
// #endif
|
||
|
||
// #ifndef MP-WEIXIN
|
||
// 其他平台(H5/App)使用标准粘贴处理
|
||
console.log('Non-MP-WEIXIN: Standard paste handling');
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
let pasteText = '';
|
||
if (e.clipboardData && e.clipboardData.getData) {
|
||
pasteText = e.clipboardData.getData('text');
|
||
console.log('Got pasteText from clipboardData:', pasteText);
|
||
}
|
||
|
||
if (pasteText) {
|
||
// 处理粘贴的文本
|
||
handlePaste(pasteText);
|
||
} else {
|
||
// 使用异步方式获取剪贴板
|
||
setTimeout(() => {
|
||
pasteFromClipboard();
|
||
}, 100);
|
||
}
|
||
|
||
return false;
|
||
// #endif
|
||
};
|
||
|
||
// 处理粘贴的文本
|
||
const handlePaste = (text) => {
|
||
// 提取数字
|
||
const digits = text.replace(/\D/g, '');
|
||
console.log('Extracted digits from paste:', digits);
|
||
|
||
if (digits.length === 0) {
|
||
uni.showToast({ title: '剪贴板中没有找到验证码', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
// 限制为6位数字
|
||
const code = digits.substring(0, 6);
|
||
smsCode.value = code;
|
||
|
||
uni.showToast({ title: '已粘贴验证码', icon: 'success' });
|
||
|
||
// 如果粘贴了6位数字,可以自动提交(可选)
|
||
if (code.length === 6) {
|
||
console.log('6位验证码已通过粘贴输入完整');
|
||
}
|
||
};
|
||
|
||
// 从剪贴板粘贴验证码
|
||
const pasteFromClipboard = () => {
|
||
console.log('pasteFromClipboard called');
|
||
|
||
uni.getClipboardData({
|
||
success: (res) => {
|
||
console.log('getClipboardData success, data:', res.data);
|
||
const text = res.data || '';
|
||
handlePaste(text);
|
||
},
|
||
fail: (err) => {
|
||
console.error('读取剪贴板失败:', err);
|
||
uni.showToast({ title: '读取剪贴板失败', icon: 'none' });
|
||
}
|
||
});
|
||
};
|
||
|
||
// 提交验证
|
||
const submitVerification = async () => {
|
||
console.log('submitVerification called, canSubmit:', canSubmit.value, 'loading:', loading.value);
|
||
|
||
if (!canSubmit.value) {
|
||
console.log('不能提交,直接返回');
|
||
return;
|
||
}
|
||
|
||
console.log('开始提交验证,设置loading为true');
|
||
loading.value = true;
|
||
const code = smsCode.value;
|
||
console.log('提交的验证码:', code, '手机号:', phone.value);
|
||
|
||
// 检查手机号是否为空
|
||
if (!phone.value) {
|
||
console.error('手机号为空,无法提交验证');
|
||
uni.showToast({ title: '手机号获取失败,请返回重试', icon: 'none' });
|
||
loading.value = false;
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 调用接口 /app/appLoginPhone
|
||
const res = await $api.createRequest('/app/appLoginPhone', {
|
||
smsCode: code,
|
||
phone: phone.value,
|
||
openid: openid.value,
|
||
unionid: unionid.value,
|
||
userType: userType.value,
|
||
orgType: orgType.value
|
||
}, 'post');
|
||
|
||
console.log('短信验证接口返回:', res);
|
||
|
||
if (res.token) {
|
||
// 登录成功,存储token
|
||
const userStore = useUserStore();
|
||
await userStore.loginSetToken(res.token);
|
||
|
||
// 更新用户类型到缓存
|
||
if (res.isCompanyUser !== undefined) {
|
||
const userInfo = uni.getStorageSync('userInfo') || {};
|
||
userInfo.isCompanyUser = res.isCompanyUser ? 0 : 1;
|
||
uni.setStorageSync('userInfo', userInfo);
|
||
}
|
||
|
||
uni.showToast({ title: '登录成功', icon: 'success' });
|
||
|
||
// 返回上一页或跳转到首页
|
||
setTimeout(() => {
|
||
uni.navigateBack({ delta: 2 }); // 返回两页(跳过登录页面)
|
||
}, 1500);
|
||
} else {
|
||
uni.showToast({ title: res.msg || '验证失败', icon: 'none' });
|
||
}
|
||
} catch (error) {
|
||
console.error('短信验证失败:', error);
|
||
uni.showToast({ title: error.msg || '验证失败,请重试', icon: 'none' });
|
||
} finally {
|
||
console.log('submitVerification finally块执行,设置loading为false');
|
||
loading.value = false;
|
||
console.log('loading设置完成,当前loading值:', loading.value);
|
||
}
|
||
};
|
||
|
||
// 返回上一页
|
||
const goBack = () => {
|
||
uni.navigateBack();
|
||
};
|
||
|
||
// 组件销毁时清除定时器
|
||
onUnmounted(() => {
|
||
clearInterval(timer.value);
|
||
});
|
||
|
||
// 需要导入useUserStore
|
||
import useUserStore from '@/stores/useUserStore';
|
||
</script>
|
||
|
||
<style lang="stylus" scoped>
|
||
.sms-verify-page
|
||
min-height: 100vh
|
||
background: #FFFFFF
|
||
|
||
.nav-bar
|
||
display: flex
|
||
align-items: center
|
||
justify-content: space-between
|
||
height: 88rpx
|
||
padding: 0 32rpx
|
||
border-bottom: 1rpx solid #f5f5f5
|
||
|
||
.nav-back
|
||
width: 60rpx
|
||
height: 60rpx
|
||
display: flex
|
||
align-items: center
|
||
justify-content: center
|
||
|
||
.nav-title
|
||
font-size: 32rpx
|
||
font-weight: 600
|
||
color: #333333
|
||
|
||
.nav-placeholder
|
||
width: 60rpx
|
||
|
||
.page-content
|
||
padding: 60rpx 40rpx 40rpx
|
||
|
||
.security-tip
|
||
display: flex
|
||
align-items: center
|
||
justify-content: center
|
||
margin-bottom: 40rpx
|
||
|
||
.tip-icon
|
||
margin-right: 16rpx
|
||
|
||
.tip-text
|
||
font-size: 28rpx
|
||
font-weight: 500
|
||
color: #333333
|
||
|
||
.phone-display
|
||
text-align: center
|
||
margin-bottom: 60rpx
|
||
|
||
.phone-label
|
||
font-size: 26rpx
|
||
color: #666666
|
||
margin-bottom: 12rpx
|
||
|
||
.phone-number
|
||
font-size: 36rpx
|
||
font-weight: 600
|
||
color: #333333
|
||
|
||
.sms-input-area
|
||
margin-bottom: 40rpx
|
||
|
||
.input-label
|
||
font-size: 28rpx
|
||
color: #666666
|
||
text-align: center
|
||
margin-bottom: 20rpx
|
||
|
||
.single-input-container
|
||
margin-bottom: 16rpx
|
||
|
||
.single-input
|
||
width: 100%
|
||
height: 100rpx
|
||
border: 2rpx solid #E5E5E5
|
||
border-radius: 16rpx
|
||
font-size: 40rpx
|
||
font-weight: 600
|
||
color: #333333
|
||
background: #F7F8FA
|
||
text-align: center
|
||
padding: 0 32rpx
|
||
box-sizing: border-box
|
||
outline: none
|
||
caret-color: #256BFA
|
||
transition: all 0.3s ease
|
||
|
||
&:focus
|
||
border-color: #256BFA
|
||
background: #FFFFFF
|
||
box-shadow: 0 4rpx 12rpx rgba(37, 107, 250, 0.15)
|
||
|
||
&::placeholder
|
||
color: #999999
|
||
font-size: 32rpx
|
||
font-weight: normal
|
||
|
||
.input-hint
|
||
font-size: 24rpx
|
||
color: #999999
|
||
text-align: center
|
||
|
||
.countdown-section
|
||
text-align: center
|
||
margin-bottom: 60rpx
|
||
|
||
.countdown-text
|
||
font-size: 26rpx
|
||
color: #999999
|
||
|
||
.resend-text
|
||
font-size: 26rpx
|
||
color: #256BFA
|
||
text-decoration: underline
|
||
cursor: pointer
|
||
|
||
.paste-section
|
||
margin-top: 20rpx
|
||
|
||
.paste-text
|
||
display: inline-flex
|
||
align-items: center
|
||
justify-content: center
|
||
font-size: 26rpx
|
||
color: #256BFA
|
||
cursor: pointer
|
||
|
||
text
|
||
margin-left: 8rpx
|
||
|
||
.submit-btn
|
||
width: 100%
|
||
height: 88rpx
|
||
background: linear-gradient(135deg, #256BFA 0%, #1E5BFF 100%)
|
||
border-radius: 44rpx
|
||
color: #FFFFFF
|
||
font-size: 32rpx
|
||
font-weight: 500
|
||
border: none
|
||
display: flex
|
||
align-items: center
|
||
justify-content: center
|
||
|
||
&:disabled
|
||
opacity: 0.5
|
||
cursor: not-allowed
|
||
|
||
.loading-spinner
|
||
width: 40rpx
|
||
height: 40rpx
|
||
border: 4rpx solid rgba(255, 255, 255, 0.3)
|
||
border-radius: 50%
|
||
border-top: 4rpx solid #FFFFFF
|
||
animation: spin 1s linear infinite
|
||
|
||
// 按钮重置样式
|
||
button::after
|
||
border: none
|
||
|
||
// 动画定义
|
||
@keyframes spin
|
||
0%
|
||
transform: rotate(0deg)
|
||
100%
|
||
transform: rotate(360deg)
|
||
</style> |