Files
airz-uniapp/packages/finance/confirm/confirm.uvue
T

175 lines
14 KiB
Plaintext
Raw Normal View History

2026-09-10 10:57:40 +08:00
<template>
<view class="page page-white" :style="pageStyle()">
2026-09-10 10:57:40 +08:00
<AppHeader title="确认信息" />
<scroll-view scroll-y :style="contentStyle(96, false)">
<view class="confirm-content">
<view class="confirm-progress"><view class="progress-line"><view class="progress-segment" v-for="i in 5" :key="i"></view></view><text class="progress-count">5/5</text></view>
<view class="confirm-tip"><view class="tip-icon"><text class="tip-mark">✓</text></view><view class="tip-copy"><text class="tip-title">{{ assessmentType == 'enterprise' ? '企业融资信息' : '个人融资信息' }}</text><text class="tip-desc">请确认后再开始评估,可随时返回修改。</text></view></view>
<text v-if="!hasInput" class="empty-hint">还没有评估信息,请先完成信息填写。</text>
<view class="info-section" v-for="section in sections" :key="section.title"><view class="section-bar"><text class="section-name">{{ section.title }}</text><text class="edit" @click="edit(section.step)">编辑</text></view><template v-for="(row, index) in section.rows" :key="row.label"><view v-if="index < 3 || section.expanded" class="info-row"><text class="info-label">{{ row.label }}</text><text class="info-value">{{ row.value }}</text></view></template><text v-if="section.rows.length > 3" class="expand-link" @click="section.expanded = !section.expanded">{{ section.expanded ? '收起详情' : '查看其余信息' }}</text></view>
<text class="assessment-note">本次评估只分析自填信息。照片单独保存,尚未核验,暂不参与评估;外部查询尚未开放,请取消授权选择。评估结果不代表授信承诺,不会向金融机构提交贷款申请。</text>
<view class="scroll-spacer" :style="bottomStyle(24)"></view>
</view>
</scroll-view>
<view class="submit-bar" :style="bottomStyle(20)"><button class="primary-button submit-button" :disabled="submitting" @click="submit">{{ !hasInput ? '去填写信息' : (submitting ? '提交中…' : '确认提交资料') }}</button></view>
2026-09-10 10:57:40 +08:00
</view>
</template>
<script setup lang="uts">
import { wanAmountValid } from '@/services/money.uts'
import { getSession } from '@/services/api.uts'
import { usePageLayout } from '@/services/layout.uts'
2026-09-10 10:57:40 +08:00
import AppHeader from '@/components/AppHeader.uvue'
import { submitAssessment } from '@/packages/finance/services/api.uts'
import { activeAssessmentType, getAssessmentInput, draftText, documentsFromInput, ownershipFromInput, formFromInput, assetValidationMessage, assetNotApplicableFields, contributionValidationMessage, clearAssessmentFlow } from '@/services/assessment.uts'
const { pageStyle, contentStyle, bottomStyle } = usePageLayout()
type InfoRow = { label: string, value: string }
type InfoSection = { title: string, step: number, rows: InfoRow[], expanded: boolean }
const submitting = ref(false)
const hasInput = ref(false)
const assessmentType = ref('personal')
const sections = ref<InfoSection[]>([])
const payload = ref<UTSJSONObject>({ source: 'mobile', confirmed: false })
let visible = false; let generation = 0; let ownerToken = ''; let accountTimer = 0
const stopAccountTimer = () => { if (accountTimer != 0) clearInterval(accountTimer); accountTimer = 0 }
const clearView = () => { hasInput.value = false; sections.value = []; payload.value = { source: 'mobile', confirmed: false }; submitting.value = false }
const sameOwner = (): boolean => {
if (!visible) return false
if (ownerToken.length > 0 && getSession()?.getString('token') == ownerToken) return true
generation++; clearView(); clearAssessmentFlow(); stopAccountTimer()
if (getSession() == null) uni.redirectTo({ url: '/packages/account/login/login' })
else uni.showToast({ title: '账户已变化,请重新填写。', icon: 'none' })
return false
}
const mask = (value: string, left: number, right: number): string => { if (value.length <= left + right) return value; return value.substring(0, left) + '****' + value.substring(value.length - right) }
const displayValue = (saved: UTSJSONObject, key: string): string => { const value = draftText(saved, key); return value.length > 0 ? value : '未填写' }
const appendRow = (rows: InfoRow[], label: string, value: string) => { const row: InfoRow = { label, value }; rows.push(row) }
const valueWithUnit = (saved: UTSJSONObject, key: string, unit: string): string => {
const value = draftText(saved, key)
if (value.length > 0) return value + ' ' + unit
if (assetNotApplicableFields(formFromInput(saved)).includes(key)) return ['totalDebtBalance', 'monthlyLoanPayment'].includes(key) ? '不适用(无贷款)' : '不适用(无信用卡)'
return '未填写'
}
const loadInput = () => {
assessmentType.value = activeAssessmentType()
const saved = getAssessmentInput(assessmentType.value)
if (saved == null) { clearView(); return }
hasInput.value = true
const enterprise = assessmentType.value == 'enterprise'
const basicRows: InfoRow[] = []
if (enterprise) {
appendRow(basicRows, '企业名称', displayValue(saved, 'enterpriseName'))
appendRow(basicRows, '信用代码', mask(displayValue(saved, 'businessCode'), 4, 4))
}
appendRow(basicRows, enterprise ? '联系人' : '姓名', displayValue(saved, 'name'))
appendRow(basicRows, '手机号', mask(displayValue(saved, 'phone'), 3, 4))
appendRow(basicRows, '所在省份', displayValue(saved, 'city'))
if (!enterprise) {
appendRow(basicRows, '身份证号', mask(displayValue(saved, 'idCard'), 4, 4))
appendRow(basicRows, '婚姻状况', displayValue(saved, 'maritalStatus'))
}
const incomeRows: InfoRow[] = enterprise ? [
{ label: '经营行业', value: displayValue(saved, 'occupation') }, { label: '经营月数', value: valueWithUnit(saved, 'operatingMonths', '个月') }, { label: '年营业收入', value: draftText(saved, 'annualRevenue').length > 0 ? draftText(saved, 'annualRevenue') + ' 万元' : '未填写' }
] : [
{ label: '职业类型', value: displayValue(saved, 'occupation') }, { label: '月收入', value: draftText(saved, 'monthlyIncome').length > 0 ? draftText(saved, 'monthlyIncome') + ' 万元' : '未填写' }, { label: '工作单位', value: displayValue(saved, 'company') }, { label: '社保缴纳', value: displayValue(saved, 'socialSecurity') }, { label: '社保月缴存基数', value: valueWithUnit(saved, 'socialSecurityBase', '万元') }, { label: '公积金缴纳', value: displayValue(saved, 'housingFund') }, { label: '公积金月缴存基数', value: valueWithUnit(saved, 'housingFundBase', '万元') }
]
const files = documentsFromInput(saved)
const fileNames = files.map((file) => file.name).join('、')
const grants = saved['authorizations'] as string[] | null
const grantNames: string[] = []
if (grants != null) { if (grants.includes('credit')) grantNames.push('征信'); if (grants.includes('business')) grantNames.push('经营'); if (grants.includes('fund')) grantNames.push('公积金') }
sections.value = [
{ title: '基本信息', step: 0, rows: basicRows, expanded: false },
{ title: enterprise ? '经营信息' : '职业与收入', step: 1, rows: incomeRows, expanded: false },
{ title: '资产与负债', step: 2, rows: [
{ label: draftText(saved, 'loanCountScope') == 'all_loans' ? '贷款笔数' : '信用贷款笔数(旧口径)', value: valueWithUnit(saved, 'creditLoanCount', '笔') },
{ label: '贷款统计口径', value: draftText(saved, 'loanCountScope') == 'all_loans' ? '信用贷款、房贷、车贷等全部当前未结清贷款' : '旧资料仅统计信用贷款,请返回资产页重新确认' },
{ label: '欠款总余额', value: valueWithUnit(saved, 'totalDebtBalance', '万元') },
{ label: '贷款月还款金额', value: valueWithUnit(saved, 'monthlyLoanPayment', '万元') },
{ label: '银行信用卡张数', value: valueWithUnit(saved, 'creditCardCount', '张') },
{ label: '总额度', value: valueWithUnit(saved, 'creditCardTotalLimit', '万元') },
{ label: '已使用总额度', value: valueWithUnit(saved, 'creditCardUsedLimit', '万元') },
{ label: '是否有房', value: ownershipFromInput(saved, 'house').length > 0 ? ownershipFromInput(saved, 'house') : '未填写' },
{ label: '是否有车', value: ownershipFromInput(saved, 'car').length > 0 ? ownershipFromInput(saved, 'car') : '未填写' }
], expanded: false },
{ title: '资料与授权', step: 3, rows: [{ label: '已上传照片', value: fileNames.length > 0 ? fileNames : '未上传' }, { label: '授权意向', value: grantNames.length > 0 ? grantNames.join('、') : '未选择' }, { label: '当前状态', value: '照片单独保存,未核验、未参与评估;数据未查询' }], expanded: false }
]
payload.value = { ...saved, source: 'mobile', confirmed: true }
2026-09-10 10:57:40 +08:00
}
onShow(() => {
visible = true; generation++; ownerToken = getSession()?.getString('token') ?? ''
loadInput(); stopAccountTimer()
if (sameOwner()) accountTimer = setInterval(() => { sameOwner() }, 1000)
})
onHide(() => { visible = false; generation++; stopAccountTimer(); clearView() })
onUnload(() => {
visible = false; generation++; stopAccountTimer(); clearView()
const pages = getCurrentPages()
if (!pages.some((page): boolean => page.route == 'packages/finance/assessment/assessment')) clearAssessmentFlow()
})
const edit = (step: number) => {
if (!sameOwner()) return
const input = getAssessmentInput(assessmentType.value)
if (input != null) input['step'] = step
const pages = getCurrentPages()
if (pages.length > 1 && pages[pages.length - 2].route == 'packages/finance/assessment/assessment') uni.navigateBack({ delta: 1 })
else uni.navigateTo({ url: '/packages/finance/assessment/assessment?type=' + assessmentType.value })
}
const submit = async () => {
if (!sameOwner()) return
if (!hasInput.value) { edit(0); return }
if (submitting.value) return
const saved = payload.value
const amount = draftText(saved, assessmentType.value == 'enterprise' ? 'annualRevenue' : 'monthlyIncome')
if (draftText(saved, 'name').length < 2 || !/^1[3-9][0-9]{9}$/.test(draftText(saved, 'phone')) || draftText(saved, 'city').length == 0) { uni.showToast({ title: '请先完善基本信息', icon: 'none' }); edit(0); return }
if (assessmentType.value == 'personal' && !/(^[0-9]{15}$)|(^[0-9]{17}[0-9Xx]$)/.test(draftText(saved, 'idCard'))) { uni.showToast({ title: '请补全身份证信息', icon: 'none' }); edit(0); return }
if (assessmentType.value == 'enterprise' && (draftText(saved, 'enterpriseName').length < 2 || !/^[0-9A-Z]{18}$/.test(draftText(saved, 'businessCode').toUpperCase()))) { uni.showToast({ title: '请补全企业信息', icon: 'none' }); edit(0); return }
if (!wanAmountValid(amount) || parseFloat(amount) <= 0) { uni.showToast({ title: '请先完善收入信息', icon: 'none' }); edit(1); return }
const grants = saved['authorizations'] as string[] | null
if (grants != null && grants.length > 0 && saved['dataConsent'] != true) { uni.showToast({ title: '请确认授权选择', icon: 'none' }); edit(3); return }
const contributionError = assessmentType.value == 'personal' ? contributionValidationMessage(formFromInput(payload.value)) : ''
if (contributionError.length > 0) { uni.showToast({ title: contributionError, icon: 'none' }); edit(1); return }
const assetError = assetValidationMessage(formFromInput(saved))
if (assetError.length > 0) { uni.showToast({ title: assetError, icon: 'none' }); edit(2); return }
submitting.value = true
const request = ++generation
try {
const id = await submitAssessment(payload.value)
if (request != generation || !sameOwner()) return
clearAssessmentFlow()
// 清空本次填写流程和页面栈;修改须重新读取最新版本,防止返回旧表单重复提交。
uni.reLaunch({ url: '/packages/finance/analyzing/analyzing?assessmentId=' + encodeURIComponent(id) })
}
catch (error) { if (request == generation && sameOwner()) uni.showToast({ title: error instanceof Error ? error.message : '提交失败,请重试', icon: 'none' }) }
finally { if (request == generation && visible) submitting.value = false }
}
2026-09-10 10:57:40 +08:00
</script>
<style>
.confirm-content { padding: 26rpx 30rpx 0; }
.confirm-progress { flex-direction: row; align-items: center; margin-bottom: 24rpx; }
.progress-line { flex: 1; flex-direction: row; }
.progress-segment { flex: 1; height: 8rpx; margin-right: 8rpx; border-radius: 8rpx; background-color: #0868f7; }
.progress-count { font-size: 24rpx; color: #758297; margin-left: 20rpx; }
.confirm-tip { padding: 24rpx; border-radius: 18rpx; background-color: #eff6ff; flex-direction: row; align-items: center; }
.tip-icon { width: 56rpx; height: 56rpx; border-radius: 28rpx; background-color: #0868f7; align-items: center; justify-content: center; margin-right: 18rpx; flex-shrink: 0; }
.tip-mark { color: #fff; font-size: 28rpx; }
.tip-copy { flex: 1; min-width: 0; }
.tip-title { color: #22324b; font-size: 29rpx; font-weight: 600; }
.tip-desc { color: #738198; font-size: 24rpx; line-height: 36rpx; margin-top: 7rpx; }
.empty-hint { font-size: 28rpx; line-height: 44rpx; color: #778499; margin-top: 40rpx; }
.info-section { margin-top: 18rpx; border-bottom: 1rpx solid #e9eef5; padding-bottom: 14rpx; }
.section-bar { height: 74rpx; flex-direction: row; align-items: center; justify-content: space-between; }
.section-name { font-size: 29rpx; font-weight: 600; color: #22324b; }
.edit { padding: 16rpx 0 16rpx 24rpx; color: #0868f7; font-size: 25rpx; }
.info-row { min-height: 62rpx; flex-direction: row; align-items: flex-start; padding: 9rpx 0; }
.info-label { color: #758297; font-size: 25rpx; line-height: 38rpx; width: 176rpx; flex-shrink: 0; margin-right: 16rpx; }
.info-value { flex: 1; min-width: 0; text-align: right; color: #26374f; font-size: 26rpx; line-height: 38rpx; }
.expand-link { color: #7387a3; font-size: 24rpx; padding: 15rpx 0 10rpx; }
.assessment-note { font-size: 23rpx; line-height: 36rpx; color: #8490a3; margin-top: 24rpx; }
.scroll-spacer { min-height: 152rpx; }
.submit-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 30rpx; border-top: 1rpx solid #edf1f6; background-color: #fff; }
.submit-button { margin: 0; }
2026-09-10 10:57:40 +08:00
</style>