SFT/fryl_h5/src/views/steps/SmsVerifyStep.vue

183 lines
5.4 KiB
Vue

<template>
<!-- 2026-08-08 对齐企业开户交互样式改造:外层容器从"整屏 min-height: calc(100vh - 46px) +
fixed footer"改为手风琴步骤行内的普通块级布局(.step-body),不再假设整屏展示,
参照企业开户 EnterpriseSmsVerifyStep.vue;同时去掉"上一步"按钮及对应 emit,
本步骤不再支持回退 -->
<div class="step-body">
<van-cell-group inset>
<van-cell title="手机号" :value="mobilePhoneMask || '-'" />
</van-cell-group>
<div class="verify-row">
<van-field
v-model="verifyCode"
type="digit"
maxlength="6"
placeholder="请输入短信验证码"
class="verify-input"
/>
<van-button
size="small"
type="primary"
plain
class="send-btn"
:disabled="countdown > 0 || sending"
:loading="sending"
@click="handleSendCode"
>
{{ countdown > 0 ? `${countdown}s后重新获取` : '获取验证码' }}
</van-button>
</div>
<div class="footer">
<van-button
type="primary"
block
round
:disabled="!verifyCode || submitting"
:loading="submitting"
loading-text="提交中..."
@click="handleSubmit"
>
确认提交
</van-button>
</div>
<!-- 耗时操作统一等待态:提交期间整页遮罩,禁止用户进行其它操作 -->
<van-overlay :show="submitting" class="submit-overlay">
<div class="overlay-center">
<van-loading type="spinner" color="#fff" size="28px">,...</van-loading>
</div>
</van-overlay>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { showToast } from 'vant'
import { sendVerifyCodeApi, submitApplicationApi } from '@/api/personalOpen'
import { showErrorModal } from '@/utils/errorModal'
const props = defineProps({
applicationNo: { type: String, required: true },
channelNo: { type: String, default: '' },
mobile: { type: String, default: '' },
mobilePhoneMask: { type: String, default: '' },
cardNo: { type: String, default: '' },
cardName: { type: String, default: '' },
idNo: { type: String, default: '' },
// AgreementStep 生成并上传《用户支付服务协议》PDF后拿到的文件编号,提交开户时随
// mobile/submit 一起传给后端(2026-08-06 新增字段 payAgreementFileNo)
payAgreementFileNo: { type: String, default: '' }
})
const emit = defineEmits(['submitted'])
const verifyCode = ref('')
const sending = ref(false)
const submitting = ref(false)
const countdown = ref(0)
let timer = null
function startCountdown() {
countdown.value = 60
timer = setInterval(() => {
countdown.value -= 1
if (countdown.value <= 0) {
clearInterval(timer)
timer = null
}
}, 1000)
}
async function handleSendCode() {
// 2026-08-06 新增前置校验:mobile/channelNo/cardNo/cardName/idNo 均来自 draft-detail 响应
// (MobileDraftDetailVo),若后端对这条申请单没有返回这些字段(为空/null),即使真的发起请求也
// 必然被后端业务校验拒绝(报"渠道编号不能为空"/"手机号不能为空",或"该短信类型银行卡号、
// 户名、证件号码...必须填写"),不如提前拦截并给出明确提示,方便定位是具体某条申请单数据
// 缺失,而不是误以为是网络问题反复重试。
if (!props.mobile) {
showErrorModal('该开户申请缺少手机号信息,无法发送验证码,请联系柜员核实后重新生成二维码', '手机号缺失')
return
}
if (!props.channelNo) {
showErrorModal('该开户申请缺少渠道信息,无法发送验证码,请联系柜员核实后重新生成二维码', '渠道信息缺失')
return
}
if (!props.cardNo || !props.cardName || !props.idNo) {
showErrorModal(
'该开户申请缺少银行卡号/户名/证件号码信息,无法发送验证码,请联系柜员核实后重新生成二维码',
'开户信息缺失'
)
return
}
sending.value = true
try {
const res = await sendVerifyCodeApi({
channelNo: props.channelNo,
mobile: props.mobile,
cardNo: props.cardNo,
cardName: props.cardName,
idNo: props.idNo
})
if (res.data?.code === 200) {
showToast('验证码已发送')
startCountdown()
}
// 非200时全局响应拦截器已弹窗提示,这里无需重复处理
} finally {
sending.value = false
}
}
async function handleSubmit() {
submitting.value = true
try {
const res = await submitApplicationApi(props.applicationNo, verifyCode.value, props.payAgreementFileNo)
if (res.data?.code === 200) {
emit('submitted', res.data.data)
} else {
// 验证码错误等业务异常:全局拦截器已弹窗提示,停留本页允许重试,清空验证码便于重新输入
verifyCode.value = ''
}
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.step-body {
padding: 0 0 4px;
position: relative;
}
.verify-row {
display: flex;
align-items: center;
gap: 8px;
margin: 16px;
background: #fff;
border-radius: 8px;
padding: 4px 12px 4px 0;
}
.verify-input {
flex: 1;
}
.send-btn {
flex-shrink: 0;
white-space: nowrap;
}
.footer {
padding: 4px 16px 4px;
}
.submit-overlay {
display: flex;
align-items: center;
justify-content: center;
}
.overlay-center {
display: flex;
flex-direction: column;
align-items: center;
}
</style>