个人开户信息确认和企业开户信息确认样式相同

main
halo 2026-08-08 16:16:00 +08:00
parent 1ce3a6bc8c
commit 68efdbaf8c
4 changed files with 298 additions and 195 deletions

View File

@ -1,7 +1,5 @@
<template>
<div class="page">
<van-nav-bar title="个人开户信息核实" />
<!-- 加载态 -->
<div v-if="loading" class="center-box">
<van-loading type="spinner" size="24px">加载中...</van-loading>
@ -10,43 +8,91 @@
<!-- 错误态(applicationNo 缺失/无效/申请单不存在等,终态,无法继续) -->
<van-empty v-else-if="loadError" image="error" :description="loadError" class="center-box" />
<!-- 正常流程 -->
<!-- 正常流程:与企业开户H5(EnterpriseOpenMobile.vue)完全一致的"手风琴式竖直步骤列表"交互
样式顶部蓝色横幅(业务确认+银行品牌),下方1-4步骤单页内纵向排列,已完成步骤折叠为
"✓ 步骤名"一行,当前步骤展开显示内容,未开始步骤折叠为灰色"圆圈数字 步骤名"一行,整页
不支持"上一步"回退(2026-08-08 对齐企业开户交互样式改造,此前个人开户"相关协议""手机
验证"两步支持点击"上一步"回退,现已去除,详见 AgreementStep.vue/SmsVerifyStep.vue) -->
<template v-else>
<InfoConfirmStep
v-if="currentStep === 'INFO'"
:detail="draftDetail"
@next="currentStep = 'AGREEMENT'"
/>
<AgreementStep
v-else-if="currentStep === 'AGREEMENT'"
:customer-name="draftDetail?.customerName"
:certificate-number="draftDetail?.certificateNumber"
:channel-no="draftDetail?.channelNo"
@prev="currentStep = 'INFO'"
@next="handleAgreementNext"
/>
<!-- 2026-08-05 二次更新:按产品要求,客户完成"相关协议"确认后跳过人脸拍照识别环节,
直接进入手机验证+提交(SMS)步骤FaceCompareStep.vue 组件与 faceRecognizeApi 接口
调用均未删除,仅从当前向导流程里移出,如后续需要恢复该环节,重新引入
`import FaceCompareStep from './steps/FaceCompareStep.vue'`,在此处插入
`<FaceCompareStep v-model:photo="facePhoto" v-model:verified="faceVerified"
:id-no="draftDetail?.certificateNumber" :name="draftDetail?.customerName"
@prev="currentStep = 'AGREEMENT'" @next="currentStep = 'SMS'" />`,并把上面
AgreementStep @next 和下面 SmsVerifyStep @prev 改回指向 'FACE' 即可 -->
<SmsVerifyStep
v-else-if="currentStep === 'SMS'"
:application-no="applicationNo"
:channel-no="draftDetail?.channelNo"
:mobile="draftDetail?.mobilePhone"
:mobile-phone-mask="draftDetail?.mobilePhoneMask"
:card-no="draftDetail?.bankCardNumber"
:card-name="draftDetail?.customerName"
:id-no="draftDetail?.certificateNumber"
:pay-agreement-file-no="payAgreementFileNo"
@prev="currentStep = 'AGREEMENT'"
@submitted="handleSubmitted"
/>
<ResultStep v-else-if="currentStep === 'RESULT'" :result="resultData" />
<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>
<InfoConfirmStep
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>
<!-- 2026-08-05 二次更新:按产品要求,客户完成"相关协议"确认后跳过人脸拍照识别环节,
直接进入手机验证+提交(SMS)步骤FaceCompareStep.vue 组件与 faceRecognizeApi 接口
调用均未删除,仅从当前向导流程里移出,如后续需要恢复该环节,重新引入
`import FaceCompareStep from './steps/FaceCompareStep.vue'`,插入对应 step-row,
并把下面 AgreementStep @next 改回指向 'FACE' 即可 -->
<AgreementStep
v-if="currentStep === 'AGREEMENT'"
:customer-name="draftDetail?.customerName"
:certificate-number="draftDetail?.certificateNumber"
:channel-no="draftDetail?.channelNo"
@next="handleAgreementNext"
/>
</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>
<SmsVerifyStep
v-if="currentStep === 'SMS'"
:application-no="applicationNo"
:channel-no="draftDetail?.channelNo"
:mobile="draftDetail?.mobilePhone"
:mobile-phone-mask="draftDetail?.mobilePhoneMask"
:card-no="draftDetail?.bankCardNumber"
:card-name="draftDetail?.customerName"
:id-no="draftDetail?.certificateNumber"
:pay-agreement-file-no="payAgreementFileNo"
@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>
@ -69,13 +115,25 @@ const draftDetail = ref(null)
// 4:INFO() -> AGREEMENT() -> SMS(+) -> RESULT(,
// ) FACE() 2026-08-05 (),
// facePhoto/faceVerified
// facePhoto/faceVerified STEP_ORDER/isDone/rowClass H5
// (EnterpriseOpenMobile.vue),/;
// 退(2026-08-08 )
const STEP_ORDER = ['INFO', 'AGREEMENT', 'SMS', 'RESULT']
const currentStep = ref('INFO')
const resultData = ref(null)
// AgreementStep +PDF,,
// SmsVerifyStep (mobile/submit payAgreementFileNo )
const payAgreementFileNo = ref('')
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 handleAgreementNext(fileNo) {
payAgreementFileNo.value = fileNo
currentStep.value = 'SMS'
@ -119,4 +177,89 @@ onMounted(async () => {
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>

View File

@ -1,25 +1,27 @@
<template>
<div class="step-page">
<div class="content">
<div class="tip">请仔细阅读以下协议内容,勾选即代表您已阅读并同意</div>
<div class="agreement-list">
<van-checkbox v-model="agreedPayService" shape="square" class="agreement-item">
我已阅读并同意
<span class="link" @click.stop="openAgreement('PAY_SERVICE_AGREEMENT')">{{ PAY_SERVICE_AGREEMENT_TITLE }}</span>
</van-checkbox>
<van-checkbox v-model="agreedPrivacy" shape="square" class="agreement-item">
我已阅读并同意
<span class="link" @click.stop="openAgreement('PAY_SERVICE_PRIVACY')">{{ PAY_SERVICE_PRIVACY_TITLE }}</span>
</van-checkbox>
</div>
<!-- 2026-08-08 对齐企业开户交互样式改造:外层容器从"整屏 min-height: calc(100vh - 46px) +
fixed footer"改为手风琴步骤行内的普通块级布局(.step-body),不再假设整屏展示,
参照企业开户 EnterpriseAgreementStep.vue;同时去掉底部"上一步"文字链接及对应 emit,
本步骤不再支持回退 -->
<div class="step-body">
<div class="tip">请仔细阅读以下协议内容,勾选即代表您已阅读并同意</div>
<div class="agreement-list">
<van-checkbox v-model="agreedPayService" shape="square" class="agreement-item">
我已阅读并同意
<span class="link" @click.stop="openAgreement('PAY_SERVICE_AGREEMENT')">{{ PAY_SERVICE_AGREEMENT_TITLE }}</span>
</van-checkbox>
<van-checkbox v-model="agreedPrivacy" shape="square" class="agreement-item">
我已阅读并同意
<span class="link" @click.stop="openAgreement('PAY_SERVICE_PRIVACY')">{{ PAY_SERVICE_PRIVACY_TITLE }}</span>
</van-checkbox>
</div>
<!-- 2026-08-06 新增:两个协议都勾选后自动在用户支付服务协议PDF末页填入姓名/证件号码/
当前日期并上传,失败时展示重试入口;"下一步"在文件生成成功前保持禁用,避免用户带着
空的 payAgreementFileNo 进入下一步 -->
<div v-if="generateError" class="generate-error">
{{ generateError }}
<span class="retry-link" @click="generateAndUpload"></span>
</div>
<!-- 2026-08-06 新增:两个协议都勾选后自动在用户支付服务协议PDF末页填入姓名/证件号码/
当前日期并上传,失败时展示重试入口;"下一步"在文件生成成功前保持禁用,避免用户带着
空的 payAgreementFileNo 进入下一步 -->
<div v-if="generateError" class="generate-error">
{{ generateError }}
<span class="retry-link" @click="generateAndUpload"></span>
</div>
<div class="footer">
@ -34,9 +36,6 @@
>
下一步
</van-button>
<div class="footer-links">
<span @click="$emit('prev')"></span>
</div>
</div>
<!-- 耗时操作统一等待态:生成+上传协议PDF期间整页遮罩,禁止用户进行其它操作 -->
@ -79,8 +78,9 @@ const props = defineProps({
certificateNumber: { type: String, default: '' },
channelNo: { type: String, default: '' }
})
// next payAgreementFileNo, SmsVerifyStep
const emit = defineEmits(['prev', 'next'])
// next payAgreementFileNo, SmsVerifyStep
// 退, emit 'prev'(2026-08-08 )
const emit = defineEmits(['next'])
const PAY_SERVICE_AGREEMENT_TITLE = BANK_AGREEMENT_FILE_TYPE_LABELS.PAY_SERVICE_AGREEMENT
const PAY_SERVICE_PRIVACY_TITLE = BANK_AGREEMENT_FILE_TYPE_LABELS.PAY_SERVICE_PRIVACY
@ -214,14 +214,8 @@ onBeforeUnmount(() => {
</script>
<style scoped>
.step-page {
display: flex;
flex-direction: column;
min-height: calc(100vh - 46px);
}
.content {
flex: 1;
padding: 24px 16px;
.step-body {
padding: 4px 16px 4px;
}
.tip {
font-size: 14px;
@ -267,22 +261,7 @@ onBeforeUnmount(() => {
align-items: center;
}
.footer {
padding: 12px 16px 20px;
background: #fff;
}
.footer-links {
display: flex;
align-items: center;
justify-content: center;
margin-top: 14px;
font-size: 13px;
color: #969799;
}
.footer-links span {
cursor: pointer;
}
.footer-links span:hover {
color: #1989fa;
padding: 12px 0 4px;
}
.preview-modal {
height: 100%;

View File

@ -1,61 +1,62 @@
<template>
<div class="step-page">
<div class="content">
<!-- 关键信息摘要卡片:按最新原型截图新增,汇总客户名称/证件号码/手机号/账号/业务类型/
办理渠道六项核心信息"业务类型""办理渠道"为本页面固定文案( H5 工程仅服务"个人
账户开立·线上"这一单一场景,不是后端返回的业务字段,MobileDraftDetailVo 无对应字段,
详见缺失的后端接口.mdPart 17),其余四项取自已有的 draftDetail -->
<div class="summary-card">
<div class="summary-title">
<van-icon name="records" class="summary-icon" />
<span>信息确认</span>
<!-- 2026-08-08 对齐企业开户交互样式改造:外层容器从"整屏 min-height: calc(100vh - 46px) +
fixed footer"改为手风琴步骤行内的普通块级布局(.step-body),不再假设整屏展示,
参照企业开户 EnterpriseInfoConfirmStep.vue -->
<div class="step-body">
<!-- 关键信息摘要卡片:按最新原型截图新增,汇总客户名称/证件号码/手机号/账号/业务类型/
办理渠道六项核心信息"业务类型""办理渠道"为本页面固定文案( H5 工程仅服务"个人
账户开立·线上"这一单一场景,不是后端返回的业务字段,MobileDraftDetailVo 无对应字段,
详见缺失的后端接口.mdPart 17),其余四项取自已有的 draftDetail -->
<div class="summary-card">
<div class="summary-title">
<van-icon name="records" class="summary-icon" />
<span>信息确认</span>
</div>
<div class="summary-body">
<div class="summary-row">
<span class="label">客户名称</span>
<span class="value">{{ detail?.customerName || '-' }}</span>
</div>
<div class="summary-body">
<div class="summary-row">
<span class="label">客户名称</span>
<span class="value">{{ detail?.customerName || '-' }}</span>
</div>
<div class="summary-row">
<span class="label">证件号码</span>
<span class="value">{{ maskedCertificateNumber }}</span>
</div>
<div class="summary-row">
<span class="label">手机号</span>
<span class="value">{{ detail?.mobilePhoneMask || '-' }}</span>
</div>
<div class="summary-row">
<span class="label">账号</span>
<span class="value">{{ maskedBankCardNumber }}</span>
</div>
<div class="summary-row">
<span class="label">业务类型</span>
<span class="value">{{ BUSINESS_TYPE_LABEL }}</span>
</div>
<div class="summary-row">
<span class="label">办理渠道</span>
<span class="value">{{ CHANNEL_LABEL }}</span>
</div>
<div class="summary-row">
<span class="label">证件号码</span>
<span class="value">{{ maskedCertificateNumber }}</span>
</div>
<div class="summary-row">
<span class="label">手机号</span>
<span class="value">{{ detail?.mobilePhoneMask || '-' }}</span>
</div>
<div class="summary-row">
<span class="label">账号</span>
<span class="value">{{ maskedBankCardNumber }}</span>
</div>
<div class="summary-row">
<span class="label">业务类型</span>
<span class="value">{{ BUSINESS_TYPE_LABEL }}</span>
</div>
<div class="summary-row">
<span class="label">办理渠道</span>
<span class="value">{{ CHANNEL_LABEL }}</span>
</div>
</div>
<!-- 原有开户信息明细,按要求全部保留,仅整体挪到摘要卡片下方 -->
<van-cell-group inset title="更多开户信息">
<van-cell title="客户姓名" :value="detail?.customerName || '-'" />
<van-cell title="证件类型" :value="certificateTypeLabel" />
<van-cell title="证件号码" :value="maskedCertificateNumber" />
<van-cell title="证件有效期起" :value="detail?.certificateEffectiveDate || '-'" />
<van-cell title="证件失效日期" :value="detail?.certificateExpiryDate || '-'" />
<van-cell title="签发机关" :value="detail?.issuingAuthority || '-'" />
<van-cell title="证件地址" :value="detail?.certificateAddress || '-'" />
<van-cell title="性别" :value="genderLabel" />
<van-cell title="民族" :value="detail?.ethnic || '-'" />
<van-cell title="职业" :value="occupationLabel" />
<van-cell v-if="detail?.occupation === 'OTHER'" title="职业备注" :value="detail?.occupationNote || '-'" />
<van-cell title="银行卡号" :value="maskedBankCardNumber" />
<van-cell title="手机号" :value="detail?.mobilePhoneMask || '-'" />
</van-cell-group>
</div>
<!-- 原有开户信息明细,按要求全部保留,仅整体挪到摘要卡片下方 -->
<van-cell-group inset title="更多开户信息">
<van-cell title="客户姓名" :value="detail?.customerName || '-'" />
<van-cell title="证件类型" :value="certificateTypeLabel" />
<van-cell title="证件号码" :value="maskedCertificateNumber" />
<van-cell title="证件有效期起" :value="detail?.certificateEffectiveDate || '-'" />
<van-cell title="证件失效日期" :value="detail?.certificateExpiryDate || '-'" />
<van-cell title="签发机关" :value="detail?.issuingAuthority || '-'" />
<van-cell title="证件地址" :value="detail?.certificateAddress || '-'" />
<van-cell title="性别" :value="genderLabel" />
<van-cell title="民族" :value="detail?.ethnic || '-'" />
<van-cell title="职业" :value="occupationLabel" />
<van-cell v-if="detail?.occupation === 'OTHER'" title="职业备注" :value="detail?.occupationNote || '-'" />
<van-cell title="银行卡号" :value="maskedBankCardNumber" />
<van-cell title="手机号" :value="detail?.mobilePhoneMask || '-'" />
</van-cell-group>
<!-- 相关协议的勾选/展示已独立为单独的"相关协议"步骤(AgreementStep.vue),本步骤(信息核对)
只负责信息展示与确认,不再耦合协议勾选,详见 docs/superpowers/specs/
2026-07-27-personal-open-mobile-h5-design.md 2026-08-05 更新记录 -->
@ -89,15 +90,8 @@ const maskedBankCardNumber = computed(() => maskBankCard(props.detail?.bankCardN
</script>
<style scoped>
.step-page {
display: flex;
flex-direction: column;
min-height: calc(100vh - 46px);
}
.content {
flex: 1;
padding: 12px 0 16px;
overflow-y: auto;
.step-body {
padding: 0 0 4px;
}
.summary-card {
margin: 0 16px 12px;
@ -144,7 +138,6 @@ const maskedBankCardNumber = computed(() => maskBankCard(props.detail?.bankCardN
text-align: right;
}
.footer {
padding: 12px 16px 20px;
background: #fff;
padding: 16px 16px 4px;
}
</style>

View File

@ -1,38 +1,39 @@
<template>
<div class="step-page">
<div class="content">
<van-cell-group inset>
<van-cell title="手机号" :value="mobilePhoneMask || '-'" />
</van-cell-group>
<!-- 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="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 plain round class="prev-btn" :disabled="submitting" @click="$emit('prev')"></van-button>
<van-button
type="primary"
block
round
class="next-btn"
:disabled="!verifyCode || submitting"
:loading="submitting"
loading-text="提交中..."
@ -69,7 +70,7 @@ const props = defineProps({
// mobile/submit (2026-08-06 payAgreementFileNo)
payAgreementFileNo: { type: String, default: '' }
})
const emit = defineEmits(['prev', 'submitted'])
const emit = defineEmits(['submitted'])
const verifyCode = ref('')
const sending = ref(false)
@ -145,16 +146,10 @@ async function handleSubmit() {
</script>
<style scoped>
.step-page {
display: flex;
flex-direction: column;
min-height: calc(100vh - 46px);
.step-body {
padding: 0 0 4px;
position: relative;
}
.content {
flex: 1;
padding: 12px 0 16px;
}
.verify-row {
display: flex;
align-items: center;
@ -172,14 +167,7 @@ async function handleSubmit() {
white-space: nowrap;
}
.footer {
padding: 12px 16px 20px;
background: #fff;
display: flex;
gap: 12px;
}
.prev-btn,
.next-btn {
flex: 1;
padding: 4px 16px 4px;
}
.submit-overlay {
display: flex;