Files
ks-app-employment-service/pages/login/sms-verify.vue

627 lines
19 KiB
Vue
Raw Normal View History

2026-04-10 01:01:03 +08:00
<template>
<view class="sms-verify-page">
<!-- 顶部导航栏 -->
2026-04-10 11:25:34 +08:00
<!-- <view class="nav-bar">
2026-04-10 01:01:03 +08:00
<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>
2026-04-10 11:25:34 +08:00
</view> -->
2026-04-10 01:01:03 +08:00
<!-- 页面内容 -->
<view class="page-content">
<!-- 安全提示 -->
<view class="security-tip">
<view class="tip-icon">
2026-04-10 11:25:34 +08:00
<uni-icons type="auth-filled" size="36" color="#256BFA"></uni-icons>
2026-04-10 01:01:03 +08:00
</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>
<!-- 提交按钮 -->
<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';
2026-04-10 19:46:42 +08:00
import useUserStore from '@/stores/useUserStore';
import { tabbarManager } from '@/utils/tabbarManager';
2026-04-10 01:01:03 +08:00
const { $api } = inject('globalFunction');
2026-04-10 19:46:42 +08:00
const userStore = useUserStore();
2026-04-10 01:01:03 +08:00
// 从页面参数获取数据
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(() => {
if (!phone.value || phone.value.trim() === '') {
return '未知号码';
}
// 清理手机号,移除空格和特殊字符
const cleanPhone = phone.value.replace(/\D/g, '');
if (cleanPhone.length < 11) {
return phone.value;
}
const prefix = cleanPhone.substring(0, 3);
const suffix = cleanPhone.substring(cleanPhone.length - 4);
2026-04-10 11:25:34 +08:00
return `${prefix}****${suffix}`;
2026-04-10 01:01:03 +08:00
});
// 是否可以提交
const canSubmit = computed(() => {
2026-04-10 11:25:34 +08:00
return smsCode.value.length === 6 && !loading.value;
2026-04-10 01:01:03 +08:00
});
// 页面加载时获取参数
onLoad(async (options) => {
// 尝试多种可能的手机号字段名(按优先级)
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];
break;
}
}
// 如果没找到尝试从URL参数中解析
if (!foundPhone) {
const currentPages = getCurrentPages();
if (currentPages.length > 0) {
const currentPage = currentPages[currentPages.length - 1];
// 尝试从页面路由参数中获取
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;
break;
}
}
}
}
}
phone.value = foundPhone || '';
openid.value = options.openid || '';
unionid.value = options.unionid || '';
userType.value = options.userType || '';
orgType.value = options.orgType || '';
// 如果手机号仍然为空,显示错误信息
if (!phone.value) {
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;
2026-04-10 11:25:34 +08:00
// 检查手机号是否为空
if (!phone.value) {
uni.showToast({ title: '手机号获取失败,请返回重试', icon: 'none' });
return;
}
2026-04-10 01:01:03 +08:00
uni.showLoading({ title: '发送中...' });
try {
2026-04-10 11:25:34 +08:00
// 调用重新发送验证码接口
2026-04-10 13:18:15 +08:00
const requestParams = { phone: phone.value };
// 只有单位用户才传递机构类型
if (userType.value === '0') {
requestParams.orgType = orgType.value;
requestParams.userType = userType.value;
}
const res = await $api.createRequest('/app/sendSmsAgain', requestParams, 'post');
// 检查状态码
if (res.code === 200 ) {
uni.hideLoading();
uni.showToast({ title: '验证码已发送', icon: 'success' });
startCountdown();
} else {
uni.hideLoading();
uni.showToast({ title: res.msg || '发送失败', icon: 'none' });
}
2026-04-10 01:01:03 +08:00
} catch (error) {
uni.hideLoading();
2026-04-10 13:18:15 +08:00
uni.showToast({ title: error.msg || '发送失败,请重试', icon: 'none' });
2026-04-10 01:01:03 +08:00
}
};
// 验证码输入处理
const onCodeInput = (e) => {
let value = e.detail.value.replace(/\D/g, ''); // 只保留数字
// 限制为6位数字
if (value.length > 6) {
value = value.substring(0, 6);
}
smsCode.value = value;
};
// 粘贴事件处理
const onPaste = (e) => {
// #ifdef MP-WEIXIN
// 微信小程序中,使用异步方式获取剪贴板
setTimeout(() => {
pasteFromClipboard();
}, 50);
// 尝试阻止默认行为
if (e && e.preventDefault) {
e.preventDefault();
}
if (e && e.stopPropagation) {
e.stopPropagation();
}
// #endif
// #ifndef MP-WEIXIN
// 其他平台H5/App使用标准粘贴处理
e.preventDefault();
e.stopPropagation();
let pasteText = '';
if (e.clipboardData && e.clipboardData.getData) {
pasteText = e.clipboardData.getData('text');
}
if (pasteText) {
// 处理粘贴的文本
handlePaste(pasteText);
} else {
// 使用异步方式获取剪贴板
setTimeout(() => {
pasteFromClipboard();
}, 100);
}
return false;
// #endif
};
// 处理粘贴的文本
const handlePaste = (text) => {
// 提取数字
const digits = text.replace(/\D/g, '');
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' });
};
// 从剪贴板粘贴验证码
const pasteFromClipboard = () => {
uni.getClipboardData({
success: (res) => {
const text = res.data || '';
handlePaste(text);
},
2026-04-10 11:25:34 +08:00
fail: () => {
2026-04-10 01:01:03 +08:00
uni.showToast({ title: '读取剪贴板失败', icon: 'none' });
}
});
};
// 提交验证
const submitVerification = async () => {
if (!canSubmit.value) {
return;
}
loading.value = true;
const code = smsCode.value;
// 检查手机号是否为空
if (!phone.value) {
uni.showToast({ title: '手机号获取失败,请返回重试', icon: 'none' });
loading.value = false;
return;
}
try {
// 调用接口 /app/appLoginPhone
2026-04-10 13:18:15 +08:00
const requestParams = {
2026-04-10 01:01:03 +08:00
smsCode: code,
phone: phone.value,
openid: openid.value,
unionid: unionid.value,
2026-04-10 13:18:15 +08:00
userType: userType.value
};
// 只有单位用户才传递机构类型
if (userType.value === '0') {
requestParams.orgType = orgType.value;
}
const res = await $api.createRequest('/app/appLoginPhone', requestParams, 'post');
2026-04-10 01:01:03 +08:00
2026-04-10 13:18:15 +08:00
if (res.token && res.code === 200) {
2026-04-10 01:01:03 +08:00
// 登录成功存储token
2026-04-10 19:46:42 +08:00
await userStore.loginSetToken(res.token).then((resume) => {
// 更新用户类型到缓存
if (res.isCompanyUser !== undefined) {
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = Number(res.isCompanyUser); // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
uni.showToast({ title: '登录成功', icon: 'success' });
// 刷新tabbar以显示正确的用户类型
tabbarManager.refreshTabBar();
console.log(userType.value , res.isCompanyUser);
console.log('用户登录成功,简历信息:', resume);
if (!resume?.data?.jobTitleId) {
if (!res.idCard) {
if (userType.value === '1') {
// 求职者跳转到个人信息补全页面
uni.reLaunch({
url: '/packageA/pages/complete-info/complete-info?step=1'
});
} else if (userType.value === '0') {
// 招聘者跳转到企业信息补全页面
uni.reLaunch({
url: '/packageA/pages/complete-info/company-info'
});
}
}
} else {
console.log('用户登录成功,有简历信息');
// 跳转到首页
uni.reLaunch({
url: '/pages/index/index'
});
}
}).catch((error) => {
// 只有在非企业用户且确实需要简历信息时才显示错误
// 企业用户可能没有简历信息,这是正常的
if (userType.value !== '0') {
uni.showToast({ title: '获取用户信息失败', icon: 'none' });
} else {
console.log('企业用户登录成功,简历信息可能为空');
}
});
2026-04-10 01:01:03 +08:00
} else {
uni.showToast({ title: res.msg || '验证失败', icon: 'none' });
}
} catch (error) {
2026-04-10 13:18:15 +08:00
uni.showToast({ title: error || '验证失败,请重试', icon: 'none' });
2026-04-10 01:01:03 +08:00
} finally {
loading.value = false;
}
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 组件销毁时清除定时器
onUnmounted(() => {
clearInterval(timer.value);
});
2026-04-10 19:46:42 +08:00
2026-04-10 01:01:03 +08:00
</script>
<style lang="stylus" scoped>
.sms-verify-page
min-height: 100vh
2026-04-10 11:25:34 +08:00
background: linear-gradient(135deg, #F7F9FF 0%, #FFFFFF 100%)
2026-04-10 01:01:03 +08:00
.nav-bar
display: flex
align-items: center
justify-content: space-between
height: 88rpx
padding: 0 32rpx
2026-04-10 11:25:34 +08:00
background: #FFFFFF
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04)
2026-04-10 01:01:03 +08:00
.nav-back
width: 60rpx
height: 60rpx
display: flex
align-items: center
justify-content: center
2026-04-10 11:25:34 +08:00
border-radius: 50%
transition: background-color 0.2s ease
&:active
background-color: #F5F5F5
2026-04-10 01:01:03 +08:00
.nav-title
font-size: 32rpx
font-weight: 600
color: #333333
.nav-placeholder
width: 60rpx
.page-content
2026-04-10 11:25:34 +08:00
padding: 80rpx 48rpx 48rpx
2026-04-10 01:01:03 +08:00
.security-tip
display: flex
2026-04-10 11:25:34 +08:00
flex-direction: column
2026-04-10 01:01:03 +08:00
align-items: center
justify-content: center
2026-04-10 11:25:34 +08:00
margin-bottom: 60rpx
padding: 32rpx
background: linear-gradient(135deg, rgba(37, 107, 250, 0.08) 0%, rgba(30, 91, 255, 0.04) 100%)
border-radius: 24rpx
border: 1rpx solid rgba(37, 107, 250, 0.1)
2026-04-10 01:01:03 +08:00
.tip-icon
2026-04-10 11:25:34 +08:00
margin-bottom: 20rpx
width: 64rpx
height: 64rpx
display: flex
align-items: center
justify-content: center
background: #FFFFFF
border-radius: 50%
box-shadow: 0 4rpx 16rpx rgba(37, 107, 250, 0.15)
2026-04-10 01:01:03 +08:00
.tip-text
font-size: 28rpx
font-weight: 500
2026-04-10 11:25:34 +08:00
color: #256BFA
text-align: center
line-height: 1.4
2026-04-10 01:01:03 +08:00
.phone-display
text-align: center
2026-04-10 11:25:34 +08:00
margin-bottom: 80rpx
2026-04-10 01:01:03 +08:00
.phone-label
2026-04-10 11:25:34 +08:00
font-size: 28rpx
2026-04-10 01:01:03 +08:00
color: #666666
2026-04-10 11:25:34 +08:00
margin-bottom: 16rpx
opacity: 0.8
2026-04-10 01:01:03 +08:00
.phone-number
2026-04-10 11:25:34 +08:00
font-size: 44rpx
font-weight: 700
2026-04-10 01:01:03 +08:00
color: #333333
2026-04-10 11:25:34 +08:00
letter-spacing: 2rpx
background: linear-gradient(135deg, #256BFA 0%, #1E5BFF 100%)
-webkit-background-clip: text
-webkit-text-fill-color: transparent
background-clip: text
2026-04-10 01:01:03 +08:00
.sms-input-area
2026-04-10 11:25:34 +08:00
margin-bottom: 60rpx
2026-04-10 01:01:03 +08:00
.input-label
font-size: 28rpx
color: #666666
text-align: center
2026-04-10 11:25:34 +08:00
margin-bottom: 24rpx
font-weight: 500
2026-04-10 01:01:03 +08:00
.single-input-container
margin-bottom: 16rpx
.single-input
width: 100%
2026-04-10 11:25:34 +08:00
height: 120rpx
border: 3rpx solid #E8ECF4
border-radius: 20rpx
font-size: 48rpx
font-weight: 700
2026-04-10 01:01:03 +08:00
color: #333333
2026-04-10 11:25:34 +08:00
background: #FFFFFF
2026-04-10 01:01:03 +08:00
text-align: center
padding: 0 32rpx
box-sizing: border-box
outline: none
caret-color: #256BFA
transition: all 0.3s ease
2026-04-10 11:25:34 +08:00
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05)
2026-04-10 01:01:03 +08:00
&:focus
border-color: #256BFA
background: #FFFFFF
2026-04-10 11:25:34 +08:00
box-shadow: 0 8rpx 32rpx rgba(37, 107, 250, 0.2)
transform: translateY(-2rpx)
2026-04-10 01:01:03 +08:00
&::placeholder
2026-04-10 11:25:34 +08:00
color: #CCCCCC
2026-04-10 01:01:03 +08:00
font-size: 32rpx
font-weight: normal
.countdown-section
text-align: center
2026-04-10 11:25:34 +08:00
margin-bottom: 80rpx
2026-04-10 01:01:03 +08:00
.countdown-text
2026-04-10 11:25:34 +08:00
font-size: 28rpx
2026-04-10 01:01:03 +08:00
color: #999999
2026-04-10 11:25:34 +08:00
font-weight: 500
2026-04-10 01:01:03 +08:00
.resend-text
2026-04-10 11:25:34 +08:00
font-size: 28rpx
2026-04-10 01:01:03 +08:00
color: #256BFA
2026-04-10 11:25:34 +08:00
font-weight: 600
2026-04-10 01:01:03 +08:00
cursor: pointer
2026-04-10 11:25:34 +08:00
padding: 12rpx 24rpx
border-radius: 24rpx
background: rgba(37, 107, 250, 0.1)
display: inline-block
transition: all 0.2s ease
2026-04-10 01:01:03 +08:00
2026-04-10 11:25:34 +08:00
&:active
background: rgba(37, 107, 250, 0.2)
transform: scale(0.98)
2026-04-10 01:01:03 +08:00
.submit-btn
width: 100%
2026-04-10 11:25:34 +08:00
height: 96rpx
2026-04-10 01:01:03 +08:00
background: linear-gradient(135deg, #256BFA 0%, #1E5BFF 100%)
2026-04-10 11:25:34 +08:00
border-radius: 48rpx
2026-04-10 01:01:03 +08:00
color: #FFFFFF
2026-04-10 11:25:34 +08:00
font-size: 34rpx
font-weight: 600
2026-04-10 01:01:03 +08:00
border: none
display: flex
align-items: center
justify-content: center
2026-04-10 11:25:34 +08:00
box-shadow: 0 8rpx 32rpx rgba(37, 107, 250, 0.3)
transition: all 0.3s ease
position: relative
overflow: hidden
&::before
content: ''
position: absolute
top: 0
left: -100%
width: 100%
height: 100%
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent)
transition: left 0.5s ease
&:not(:disabled):hover::before
left: 100%
&:not(:disabled):active
transform: translateY(2rpx)
box-shadow: 0 4rpx 16rpx rgba(37, 107, 250, 0.3)
2026-04-10 01:01:03 +08:00
&:disabled
opacity: 0.5
cursor: not-allowed
2026-04-10 11:25:34 +08:00
box-shadow: none
2026-04-10 01:01:03 +08:00
.loading-spinner
2026-04-10 11:25:34 +08:00
width: 48rpx
height: 48rpx
2026-04-10 01:01:03 +08:00
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)
2026-04-10 11:25:34 +08:00
// 页面进入动画
.sms-verify-page
animation: fadeIn 0.4s ease-out
@keyframes fadeIn
from
opacity: 0
transform: translateY(20rpx)
to
opacity: 1
transform: translateY(0)
2026-04-10 01:01:03 +08:00
</style>