增加企业开户信息确认H5页面
parent
63555f9094
commit
2e879255ed
|
|
@ -0,0 +1,43 @@
|
||||||
|
import request from './request'
|
||||||
|
|
||||||
|
// 企业开户移动端H5确认页接口契约(2026-08-07新增,详见《缺失的后端接口.md》登记):
|
||||||
|
// - draft-detail:真实后端目前没有对应的匿名详情查询接口(与个人开户不同,企业开户后端仅提供
|
||||||
|
// confirm 这一个"直接提交确认"接口),本次前端参照个人开户 mobile/draft-detail 的契约模式
|
||||||
|
// 假设新增该接口,详情字段(尤其 id/channelName)均为前端假设,待后端确认。
|
||||||
|
// - send-verify-code:企业开户没有专属的发码接口,直接复用个人开户H5确认页在用的通用发码接口
|
||||||
|
// (该接口本身是透传给短信网关的通用参数,技术上可复用,待后端确认是否允许非个人开户场景调用)。
|
||||||
|
// tradeNo/tradeType 固定值取自个人开户H5当前已修正的正确取值(2026-08-06 修正记录,见
|
||||||
|
// personalOpen.js 同名字段注释):tradeNo 固定 "221506",tradeType 固定 "0"。
|
||||||
|
// - confirm:后端已提供的真实接口,语义为"申请人扫码核实信息后点击确认,提交凡荣e链",
|
||||||
|
// 与个人开户的 mobile/submit 不同,请求体是 {channelNo, verifyCode, confirmBto:{id,
|
||||||
|
// applicationStatus, confirmTime}},confirmBto.id 取自本次假设的 draft-detail 接口。
|
||||||
|
|
||||||
|
export const fetchDraftDetailApi = (applicationNo) =>
|
||||||
|
request.post('/api/wallet-management/enterprise-open-application/mobile/draft-detail', { applicationNo })
|
||||||
|
|
||||||
|
// cardName(银行卡户名):企业场景没有个人开户那种"卡户名与客户姓名一致"的天然对应关系,
|
||||||
|
// 企业银行账号的户名通常即企业名称,故取 enterpriseName 代替(与个人开户用 customerName
|
||||||
|
// 的处理思路一致)。
|
||||||
|
export const sendVerifyCodeApi = ({ channelNo, mobile, cardNo, cardName, idNo }) =>
|
||||||
|
request.post('/api/wallet-management/personal-opening/mobile/send-verify-code/general', {
|
||||||
|
channelNo,
|
||||||
|
tradeNo: '221506',
|
||||||
|
tradeType: '0',
|
||||||
|
mobile,
|
||||||
|
cardNo,
|
||||||
|
cardName,
|
||||||
|
idNo
|
||||||
|
})
|
||||||
|
|
||||||
|
// confirm 为真实已有接口,直接对接真实后端(不在 mock-server 实现),applicationStatus 固定传
|
||||||
|
// 'CONFIRMED'(枚举含义"已确认",对应"申请人扫码确认"这一步),confirmTime 为提交时的当前时间。
|
||||||
|
export const confirmApplicationApi = ({ channelNo, verifyCode, id, confirmTime }) =>
|
||||||
|
request.post('/api/wallet-management/enterprise-open-application/confirm', {
|
||||||
|
channelNo,
|
||||||
|
verifyCode,
|
||||||
|
confirmBto: {
|
||||||
|
id,
|
||||||
|
applicationStatus: 'CONFIRMED',
|
||||||
|
confirmTime
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// 与主项目 src/router/index.js 的定位不同:本工程无需登录鉴权/动态菜单路由,
|
// 与主项目 src/router/index.js 的定位不同:本工程无需登录鉴权/动态菜单路由,
|
||||||
// 仅一个业务路由,直接静态声明即可。
|
// 业务路由静态声明即可。
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import PersonalOpenMobile from '@/views/PersonalOpenMobile.vue'
|
import PersonalOpenMobile from '@/views/PersonalOpenMobile.vue'
|
||||||
|
import EnterpriseOpenMobile from '@/views/EnterpriseOpenMobile.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
|
|
@ -11,7 +12,12 @@ const router = createRouter({
|
||||||
name: 'PersonalOpenMobile',
|
name: 'PersonalOpenMobile',
|
||||||
component: PersonalOpenMobile
|
component: PersonalOpenMobile
|
||||||
},
|
},
|
||||||
// 兜底:任何其它路径都重定向到唯一业务页面(本工程不做多页面导航)
|
{
|
||||||
|
path: '/mobile/enterprise-open',
|
||||||
|
name: 'EnterpriseOpenMobile',
|
||||||
|
component: EnterpriseOpenMobile
|
||||||
|
},
|
||||||
|
// 兜底:未知路径重定向到个人开户页面(本工程默认业务场景,与既有行为保持一致)
|
||||||
{ path: '/:pathMatch(.*)*', redirect: '/mobile/personal-open' }
|
{ path: '/:pathMatch(.*)*', redirect: '/mobile/personal-open' }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
<!-- src/views/EnterpriseOpenMobile.vue -->
|
||||||
|
<!-- 企业开户移动端H5确认页(2026-08-07新增),扫码入口来自管理后台企业开户表单"邀请扫码办理"
|
||||||
|
生成的二维码。与个人开户H5(PersonalOpenMobile.vue)的"整屏替换单步"视觉不同,按产品提供的
|
||||||
|
原型截图实现为"手风琴式竖直步骤列表":顶部蓝色横幅(业务确认+银行品牌),下方1-4步骤单页内
|
||||||
|
纵向排列,已完成步骤折叠为"✓ 步骤名"一行,当前步骤展开显示内容,未开始步骤折叠为灰色
|
||||||
|
"圆圈数字 步骤名"一行。截图里没有任何"上一步"按钮,本页面同样不支持步骤回退(与企业开户
|
||||||
|
confirm 接口 DRAFT→CONFIRMED 的单向状态迁移语义一致)。
|
||||||
|
后端契约缺口(详见《缺失的后端接口.md》与 src/api/enterpriseOpen.js 顶部注释):
|
||||||
|
- mobile/draft-detail 是本次前端假设新增的接口,后端目前未提供
|
||||||
|
- 发验证码复用个人开户H5在用的通用接口
|
||||||
|
- 提交确认(confirm)是真实已有接口,本地 mock-server 未实现,只能连真实后端环境验证 -->
|
||||||
|
<template>
|
||||||
|
<div class="page">
|
||||||
|
<div v-if="loading" class="center-box">
|
||||||
|
<van-loading type="spinner" size="24px">加载中...</van-loading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-empty v-else-if="loadError" image="error" :description="loadError" class="center-box" />
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<div class="banner">
|
||||||
|
<span class="banner-title">业务确认</span>
|
||||||
|
<div class="banner-brand">
|
||||||
|
<span class="brand-badge">+</span>
|
||||||
|
<span class="brand-name">浙江稠州商业银行</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step-list">
|
||||||
|
<div class="step-row" :class="rowClass('INFO')">
|
||||||
|
<div class="step-row-header">
|
||||||
|
<span class="step-icon">
|
||||||
|
<van-icon v-if="isDone('INFO')" name="success" class="icon-done" />
|
||||||
|
<span v-else class="step-number">1</span>
|
||||||
|
</span>
|
||||||
|
<span class="step-label">信息核对</span>
|
||||||
|
</div>
|
||||||
|
<EnterpriseInfoConfirmStep
|
||||||
|
v-if="currentStep === 'INFO'"
|
||||||
|
:detail="draftDetail"
|
||||||
|
@next="currentStep = 'AGREEMENT'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step-row" :class="rowClass('AGREEMENT')">
|
||||||
|
<div class="step-row-header">
|
||||||
|
<span class="step-icon">
|
||||||
|
<van-icon v-if="isDone('AGREEMENT')" name="success" class="icon-done" />
|
||||||
|
<span v-else class="step-number">2</span>
|
||||||
|
</span>
|
||||||
|
<span class="step-label">相关协议</span>
|
||||||
|
</div>
|
||||||
|
<EnterpriseAgreementStep v-if="currentStep === 'AGREEMENT'" @next="currentStep = 'SMS'" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step-row" :class="rowClass('SMS')">
|
||||||
|
<div class="step-row-header">
|
||||||
|
<span class="step-icon">
|
||||||
|
<van-icon v-if="isDone('SMS')" name="success" class="icon-done" />
|
||||||
|
<span v-else class="step-number">3</span>
|
||||||
|
</span>
|
||||||
|
<span class="step-label">手机验证</span>
|
||||||
|
</div>
|
||||||
|
<EnterpriseSmsVerifyStep
|
||||||
|
v-if="currentStep === 'SMS'"
|
||||||
|
:id="draftDetail?.id"
|
||||||
|
:channel-no="draftDetail?.channelNo"
|
||||||
|
:mobile="draftDetail?.mobilePhone"
|
||||||
|
:mobile-phone-mask="draftDetail?.mobilePhoneMask"
|
||||||
|
:card-no="draftDetail?.bankCardNumber"
|
||||||
|
:card-name="draftDetail?.enterpriseName"
|
||||||
|
:id-no="draftDetail?.certificateNumber"
|
||||||
|
@submitted="handleSubmitted"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step-row" :class="rowClass('RESULT')">
|
||||||
|
<div class="step-row-header">
|
||||||
|
<span class="step-icon">
|
||||||
|
<van-icon v-if="isDone('RESULT')" name="success" class="icon-done" />
|
||||||
|
<span v-else class="step-number">4</span>
|
||||||
|
</span>
|
||||||
|
<span class="step-label">完成</span>
|
||||||
|
</div>
|
||||||
|
<ResultStep v-if="currentStep === 'RESULT'" :result="resultData" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { fetchDraftDetailApi } from '@/api/enterpriseOpen'
|
||||||
|
import EnterpriseInfoConfirmStep from './steps/enterprise/EnterpriseInfoConfirmStep.vue'
|
||||||
|
import EnterpriseAgreementStep from './steps/enterprise/EnterpriseAgreementStep.vue'
|
||||||
|
import EnterpriseSmsVerifyStep from './steps/enterprise/EnterpriseSmsVerifyStep.vue'
|
||||||
|
import ResultStep from './steps/ResultStep.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const applicationNo = route.query.applicationNo || ''
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const loadError = ref('')
|
||||||
|
const draftDetail = ref(null)
|
||||||
|
|
||||||
|
// 4个步骤:INFO(信息核对) -> AGREEMENT(相关协议) -> SMS(手机验证+提交) -> RESULT(结果页,终态)
|
||||||
|
// 截图没有任何"上一步"交互,本页面不支持步骤回退
|
||||||
|
const STEP_ORDER = ['INFO', 'AGREEMENT', 'SMS', 'RESULT']
|
||||||
|
const currentStep = ref('INFO')
|
||||||
|
const resultData = ref(null)
|
||||||
|
|
||||||
|
function isDone(key) {
|
||||||
|
return STEP_ORDER.indexOf(key) < STEP_ORDER.indexOf(currentStep.value)
|
||||||
|
}
|
||||||
|
function rowClass(key) {
|
||||||
|
if (isDone(key)) return 'is-done'
|
||||||
|
if (key === currentStep.value) return 'is-current'
|
||||||
|
return 'is-pending'
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmitted(data) {
|
||||||
|
resultData.value = data
|
||||||
|
currentStep.value = 'RESULT'
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!applicationNo) {
|
||||||
|
loading.value = false
|
||||||
|
loadError.value = '链接无效,缺少申请编号,请重新扫描二维码'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await fetchDraftDetailApi(applicationNo)
|
||||||
|
if (res.data?.code === 200) {
|
||||||
|
draftDetail.value = res.data.data
|
||||||
|
} else {
|
||||||
|
loadError.value = res.data?.message || '获取开户信息失败,请联系柜员重新生成二维码'
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 网络异常已由 request.js 响应拦截器统一提示,这里仅需要把页面切到错误态
|
||||||
|
loadError.value = '获取开户信息失败,请检查网络后重新扫描二维码'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f5f6f8;
|
||||||
|
}
|
||||||
|
.center-box {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 60vh;
|
||||||
|
}
|
||||||
|
.banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: linear-gradient(90deg, #1677ff, #4096ff);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.banner-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.banner-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.brand-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #fff;
|
||||||
|
color: #ee0a24;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.brand-name {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.step-list {
|
||||||
|
padding: 12px 16px 24px;
|
||||||
|
}
|
||||||
|
.step-row {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.step-row-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
}
|
||||||
|
.step-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.step-number {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e8e8e8;
|
||||||
|
color: #969799;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.icon-done {
|
||||||
|
color: #07c160;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.step-label {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #969799;
|
||||||
|
}
|
||||||
|
.is-current .step-number {
|
||||||
|
background: #1677ff;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.is-current .step-label {
|
||||||
|
color: #323233;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.is-done .step-label {
|
||||||
|
color: #323233;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<div class="step-body">
|
||||||
|
<div class="agreement-row">
|
||||||
|
<van-checkbox v-model="serviceAgreed">
|
||||||
|
我已阅读并同意
|
||||||
|
<span class="link" @click.stop="confirmAgreement('service')">《浙江稠州商业银行用户支付服务协议》</span>
|
||||||
|
</van-checkbox>
|
||||||
|
</div>
|
||||||
|
<div class="agreement-row">
|
||||||
|
<van-checkbox v-model="privacyAgreed">
|
||||||
|
我已阅读并同意
|
||||||
|
<span class="link" @click.stop="confirmAgreement('privacy')">《浙江稠州商业银行用户支付服务隐私政策》</span>
|
||||||
|
</van-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<van-button type="primary" block round :disabled="!bothAgreed" @click="$emit('next')">下一步</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { showConfirmDialog } from 'vant'
|
||||||
|
|
||||||
|
defineEmits(['next'])
|
||||||
|
|
||||||
|
const serviceAgreed = ref(false)
|
||||||
|
const privacyAgreed = ref(false)
|
||||||
|
const bothAgreed = computed(() => serviceAgreed.value && privacyAgreed.value)
|
||||||
|
|
||||||
|
// 协议名称/占位正文与管理后台 EnterpriseOpenForm.vue/PersonalOpenForm.vue 使用的同一套占位文案
|
||||||
|
// 保持一致(与个人开户H5 AgreementStep.vue 现在使用的"PDF填充+上传"完全不同的流程,本次企业
|
||||||
|
// 开户H5严格按截图展示的简单勾选+确认弹窗交互,不涉及PDF生成/上传)。
|
||||||
|
const AGREEMENT_CONTENT = {
|
||||||
|
service: {
|
||||||
|
title: '是否同意《浙江稠州商业银行用户支付服务协议》',
|
||||||
|
field: 'serviceAgreed'
|
||||||
|
},
|
||||||
|
privacy: {
|
||||||
|
title: '是否同意《浙江稠州商业银行用户支付服务隐私政策》',
|
||||||
|
field: 'privacyAgreed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击协议名链接:弹出确认对话框(原型截图里是嵌入式浏览器对原生 window.confirm() 的展示效果,
|
||||||
|
// 本次改用 Vant 组件化的 showConfirmDialog,与项目其余弹窗风格保持一致,不刻意还原原生浏览器
|
||||||
|
// 弹窗的域名前缀展示)。确定则勾选对应复选框,取消不做任何改动。
|
||||||
|
async function confirmAgreement(type) {
|
||||||
|
const info = AGREEMENT_CONTENT[type]
|
||||||
|
try {
|
||||||
|
await showConfirmDialog({ title: info.title, confirmButtonText: '确定', cancelButtonText: '取消' })
|
||||||
|
if (info.field === 'serviceAgreed') serviceAgreed.value = true
|
||||||
|
else privacyAgreed.value = true
|
||||||
|
} catch {
|
||||||
|
// 用户点击"取消",不做任何改动
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.step-body {
|
||||||
|
padding: 4px 16px 4px;
|
||||||
|
}
|
||||||
|
.agreement-row {
|
||||||
|
padding: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #646566;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
color: #1989fa;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding: 12px 0 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div class="step-body">
|
||||||
|
<van-cell-group inset>
|
||||||
|
<van-cell title="业务类型" value="稠州企业开户" />
|
||||||
|
<van-cell title="办理渠道" :value="dealChannelText" />
|
||||||
|
<van-cell title="客户" :value="detail?.enterpriseName || '-'" />
|
||||||
|
<van-cell title="手机号" :value="detail?.mobilePhoneMask || '-'" />
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<van-button type="primary" block round @click="$emit('next')">确认无误,下一步</van-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
detail: { type: Object, default: null }
|
||||||
|
})
|
||||||
|
defineEmits(['next'])
|
||||||
|
|
||||||
|
// "业务类型"后端无对应字段,前端固定文案展示(与截图一致);"办理渠道"取假设新增的
|
||||||
|
// channelName 字段(后端目前未提供,详见 src/api/enterpriseOpen.js 顶部注释),固定拼接
|
||||||
|
// "(线上)"后缀,因为本页面本身就是线上H5办理场景
|
||||||
|
const dealChannelText = computed(() => `${props.detail?.channelName || '-'}(线上)`)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.step-body {
|
||||||
|
padding: 0 0 4px;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
padding: 16px 16px 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
<template>
|
||||||
|
<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 dayjs from 'dayjs'
|
||||||
|
import { showToast } from 'vant'
|
||||||
|
import { sendVerifyCodeApi, confirmApplicationApi } from '@/api/enterpriseOpen'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: { type: [Number, String], default: undefined },
|
||||||
|
channelNo: { type: String, default: '' },
|
||||||
|
mobile: { type: String, default: '' },
|
||||||
|
mobilePhoneMask: { type: String, default: '' },
|
||||||
|
cardNo: { type: String, default: '' },
|
||||||
|
cardName: { type: String, default: '' },
|
||||||
|
idNo: { 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() {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交确认:直接对接真实已有的 enterprise-open-application/confirm 接口,confirmTime 取当前时间
|
||||||
|
async function handleSubmit() {
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const res = await confirmApplicationApi({
|
||||||
|
channelNo: props.channelNo,
|
||||||
|
verifyCode: verifyCode.value,
|
||||||
|
id: props.id,
|
||||||
|
confirmTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
})
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.verify-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 16px;
|
||||||
|
background: #f7f8fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px 12px 4px 0;
|
||||||
|
}
|
||||||
|
.verify-input {
|
||||||
|
flex: 1;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.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>
|
||||||
Loading…
Reference in New Issue