Files
airz-uniapp/packages/finance/next-step/next-step.uvue
T

179 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page" :style="pageStyle()">
<AppHeader title="下一步行动" :customBack="true" @back="back" />
<scroll-view scroll-y :scroll-into-view="scrollTarget" :style="contentStyle(96, false)">
<view class="content">
<text v-if="loading" class="body">正在读取本次报告与产品状态…</text>
<view v-if="error.length > 0" class="panel"><text class="heading">暂时无法查看行动建议</text><text class="body">{{ error }}</text><button v-if="routeError.length == 0" class="secondary" @click="load">重新加载</button><text class="link" @click="history">前往融资档案 ›</text></view>
<template v-if="report != null && !loading">
<text class="eyebrow">评估记录 #{{ assessmentId }} · {{ assessmentType == 'enterprise' ? '企业' : '个人' }}评估</text>
<text class="intro">根据本次报告,安排您的下一步</text>
<view class="actions">
<view class="action-row" @click="prepare"><image class="action-icon" src="/packages/finance/static/action-prepare.svg" /><view class="action-copy"><text class="action-title">{{ productId.length > 0 && !canPrepare ? '核对产品条件' : '准备申请' }}</text><text class="action-note">{{ productId.length == 0 ? '选择本次匹配产品,查看材料准备说明' : '核对产品条件,了解所需材料' }}</text></view><text class="chevron">›</text></view>
<view class="action-row" @click="openReport"><image class="action-icon" src="/packages/finance/static/action-report.svg" /><view class="action-copy"><text class="action-title">查看已保存报告</text><text class="action-note">报告已保存至融资档案,可随时查看</text></view><text class="chevron">›</text></view>
<view class="action-row" @click="optimize"><image class="action-icon" src="/packages/finance/static/action-improve.svg" /><view class="action-copy"><text class="action-title">优化资料</text><text class="action-note">核对待补充与待核实项,再发起新评估</text></view><text class="chevron">›</text></view>
<view class="action-row last" @click="consult"><image class="action-icon" src="/packages/finance/static/action-chat.svg" /><view class="action-copy"><text class="action-title">咨询AI</text><text class="action-note">结合本次报告,为您解释准备方向</text></view><text class="chevron">›</text></view>
</view>
<view v-if="section == 'prepare'" id="prepare-section" class="panel">
<text class="heading">材料准备</text>
<text v-if="productId.length == 0" class="body">请先选择本次匹配中的产品,再查看对应材料。本页不会自动替您选择产品。</text>
<template v-else-if="matchError.length > 0"><text class="body">{{ matchError }}</text><button class="secondary" @click="load">重新核对产品</button></template>
<template v-else-if="match != null">
<text class="product-title">{{ match!.product.name }}</text><text class="note">{{ match!.product.bank }} · 评估时版本 v{{ match!.product.version }}</text>
<text v-if="match!.product.demo" class="demo">演示产品,仅用于功能验证,不代表真实在售产品。</text>
<text class="status">{{ match!.stateLabel }} · {{ match!.availabilityLabel }}</text>
<template v-if="canPrepare">
<text class="label">所需材料与办理说明</text><text class="body">{{ match!.product.materials }}</text>
<text class="label">申请条件(评估时)</text><text v-for="item in match!.product.conditions" :key="item" class="body">{{ item }}</text><text v-if="match!.product.conditions.length == 0" class="body">未提供</text>
<text class="label">资料来源</text><text class="body">{{ match!.product.source.trim() == '·' ? '未提供' : match!.product.source }}</text><text class="note">{{ match!.product.sourceRef.length > 0 ? match!.product.sourceRef : '来源引用未提供' }}</text><text class="note">核对日期:{{ match!.product.checkedOn.length > 0 ? match!.product.checkedOn : '未提供' }}</text>
<text class="label">机构办理渠道</text><text class="body">未提供。请自行通过机构官方渠道核实产品、材料和办理方式。</text>
</template>
<text v-else class="body">{{ match!.state != 'eligible' ? '本产品存在不符条件或待核实项,暂不能进入申请准备。您仍可查看报告、优化资料或咨询AI。' : '产品当前不可用或状态未知,暂不能进入申请准备。历史报告与评估时产品信息仍保留。' }}</text>
</template>
<button v-if="report!.matching.status == 'completed'" class="secondary" @click="chooseProduct">{{ productId.length == 0 ? '选择本次匹配产品' : '查看本次其他产品' }}</button>
<text v-else class="note">{{ report!.version == 1 ? '旧版报告未执行产品匹配,可重新评估。' : report!.matchingMessage }}</text>
</view>
<view v-if="improveOpen" id="improve-section" class="panel">
<text class="heading">优化资料后,重新评估</text><text class="note">以下内容来自本次已保存报告。请按实际情况核对资料。</text>
<view v-for="item in missingItems" :key="item.label" class="field-row"><text class="field-label">{{ item.label }}</text><text class="field-state">{{ item.value }}</text></view>
<text v-if="missingItems.length == 0" class="body">{{ report!.version == 1 ? '旧版报告未记录逐项完整度,请参考以下报告提示。' : '本次报告没有列出待补充或格式需核实字段,自填资料仍需核验。' }}</text>
<text v-for="item in report!.dataGaps" :key="item" class="body">{{ item }}</text><text v-for="item in report!.suggestions" :key="item" class="body">{{ item }}</text>
<text class="note">将进入同类型空白表单,不带入旧资料。正式提交后生成新记录,原报告保持不变。</text><button class="primary" @click="restart">重新进行{{ assessmentType == 'enterprise' ? '企业' : '个人' }}评估</button>
</view>
<view class="notice"><text class="notice-title">重要提示</text><text class="notice-text">本页仅提供材料准备与办理引导,不向银行或其他机构提交申请。匹配结果基于自填资料,不代表授信或获批承诺;实际产品条件、材料及办理方式以机构核实为准。</text></view>
<ReportExport :assessmentId="assessmentId" @authrequired="login" @sessionchanged="load" />
</template>
<button class="home-button" @click="home">返回首页</button>
</view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import AppHeader from '@/components/AppHeader.uvue'
import ReportExport from '@/packages/finance/components/ReportExport.uvue'
import { usePageLayout } from '@/services/layout.uts'
import { getSession } from '@/services/api.uts'
import { clearAssessmentFlow } from '@/services/assessment-flow.uts'
import { nextStepRoute } from '@/services/next-step-route.uts'
import { assistantRoute } from '@/packages/finance/services/assistant.uts'
import { AnalysisReport, AssessmentField, fetchAnalysisReport, fetchAssessmentStatus } from '@/packages/finance/services/records.uts'
import { MatchView, fetchMatchDetailRecord, validRecordId } from '@/packages/finance/services/matches.uts'
const { pageStyle, contentStyle } = usePageLayout()
const assessmentId = ref(''); const productId = ref(''); const section = ref('actions'); const assessmentType = ref('')
const report = ref<AnalysisReport | null>(null); const match = ref<MatchView | null>(null)
const loading = ref(false); const error = ref(''); const matchError = ref(''); const routeError = ref(''); const improveOpen = ref(false)
const scrollTarget = ref('')
let visible = false; let generation = 0; let loadedToken = ''
const canPrepare = computed((): boolean => match.value?.state == 'eligible' && match.value?.availability == 'available')
const missingItems = computed((): AssessmentField[] => report.value == null ? [] : report.value!.completenessItems.filter((item: AssessmentField): boolean => ['待补充', '需核实'].includes(item.value)))
const reset = () => { report.value = null; match.value = null; assessmentType.value = ''; matchError.value = ''; loadedToken = ''; improveOpen.value = false; scrollTarget.value = '' }
const home = () => { uni.reLaunch({ url: '/pages/home/home' }) }
const history = () => { uni.navigateTo({ url: '/packages/finance/archive/archive' }) }
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeNextStep=1&assessmentId=' + assessmentId.value + '&productId=' + productId.value + '&section=' + section.value }) }
// 离页或账户变更后,丢弃旧请求;重新进入必须重查归属和当前可用性。
const stale = (request: number, token: string): boolean => {
if (!visible || request != generation) return true
if (getSession()?.getString('token') == token) return false
generation++; reset(); loading.value = false
if (getSession() == null) login(); else error.value = '账户已切换,请重新加载本人报告。'
return true
}
const load = async () => {
const request = ++generation; reset(); error.value = ''; loading.value = false
if (routeError.value.length > 0) { error.value = routeError.value; return }
const token = getSession()?.getString('token') ?? ''
if (token.length == 0) { login(); return }
loading.value = true
try {
const status = await fetchAssessmentStatus(assessmentId.value)
if (stale(request, token)) return
if (status.id != assessmentId.value || status.status != 'succeeded' || !['personal', 'enterprise'].includes(status.type)) throw new Error('请从本人已成功生成的报告进入下一步行动。')
const result = await fetchAnalysisReport(assessmentId.value)
if (stale(request, token)) return
report.value = result; assessmentType.value = status.type; loadedToken = token
if (productId.value.length > 0) {
try {
if (result.matching.status != 'completed') throw new Error('本报告没有可读取的产品匹配结果。')
const detail = await fetchMatchDetailRecord(assessmentId.value, productId.value)
if (stale(request, token)) return
// 材料必须来自同次评估冻结产品,不允许用公共或其他报告产品补齐。
if (detail.assessmentId != assessmentId.value || detail.resultVersion != result.version || detail.snapshotId != result.matching.snapshotId || detail.ruleVersion != result.matching.ruleVersion || detail.orderingVersion != result.matching.orderingVersion) throw new Error('产品与本报告的冻结版本不一致,请重新选择产品。')
match.value = detail.item
} catch (err) { if (!stale(request, token)) matchError.value = err instanceof Error ? err.message : '产品读取失败,请重试' }
}
} catch (err) { if (!stale(request, token)) { reset(); error.value = err instanceof Error ? err.message : '报告读取失败,请重试' } }
finally {
if (request == generation) {
loading.value = false
if (report.value != null && section.value == 'prepare') nextTick(() => { if (request == generation) scrollTarget.value = 'prepare-section' })
}
}
}
const ready = (): boolean => !stale(generation, loadedToken) && report.value != null && !loading.value
const openReport = () => { if (ready()) uni.navigateTo({ url: '/packages/finance/report/report?assessmentId=' + assessmentId.value }) }
const chooseProduct = () => { if (ready() && report.value!.matching.status == 'completed') uni.navigateTo({ url: '/packages/finance/matches/matches?assessmentId=' + assessmentId.value + '&group=eligible' }) }
const prepare = () => {
if (!ready()) return
if (section.value == 'prepare') { load(); return }
uni.redirectTo({ url: nextStepRoute(assessmentId.value, productId.value, 'prepare') })
}
const optimize = () => {
if (!ready()) return
improveOpen.value = !improveOpen.value; scrollTarget.value = ''
const request = generation
if (improveOpen.value) nextTick(() => { if (request == generation) scrollTarget.value = 'improve-section' })
}
const consult = () => {
if (!ready()) return
if (productId.value.length > 0 && match.value == null) { uni.showToast({ title: '请先重新核对产品或从报告咨询AI', icon: 'none' }); return }
uni.navigateTo({ url: assistantRoute(assessmentId.value, productId.value.length > 0 ? [productId.value] : [], 'next-step') })
}
const restart = () => {
if (!ready() || !improveOpen.value) return
clearAssessmentFlow()
uni.navigateTo({ url: '/packages/finance/assessment/assessment?type=' + assessmentType.value })
}
const back = () => {
if (getCurrentPages().length > 1) uni.navigateBack()
else if (validRecordId(assessmentId.value)) uni.redirectTo({ url: '/packages/finance/report/report?assessmentId=' + assessmentId.value })
else history()
}
onLoad((options) => {
assessmentId.value = options?.['assessmentId'] ?? ''; productId.value = options?.['productId'] ?? ''; section.value = options?.['section'] ?? 'actions'
if (nextStepRoute(assessmentId.value, productId.value, section.value).length == 0) routeError.value = '页面参数无效,请从本次报告或匹配产品重新进入。'
})
onShow(() => { visible = true; load() })
onHide(() => { visible = false; generation++; reset(); loading.value = false; error.value = '' })
onUnload(() => { visible = false; generation++; reset() })
</script>
<style>
.page { background-color: #fff; }
.content { padding: 30rpx 36rpx 64rpx; }
.eyebrow { font-size: 22rpx; color: #91a0b2; }
.intro { font-size: 27rpx; color: #536882; margin-top: 16rpx; line-height: 42rpx; }
.actions { margin-top: 26rpx; }
.action-row { flex-direction: row; align-items: center; padding: 30rpx 0; border-bottom: 1rpx solid #eef2f7; }
.last { border-bottom-width: 0; }
.action-icon { width: 88rpx; height: 88rpx; flex-shrink: 0; }
.action-copy { flex: 1; margin-left: 24rpx; }
.action-title { font-size: 31rpx; font-weight: 700; color: #263d58; }
.action-note { font-size: 23rpx; color: #8795a8; line-height: 36rpx; margin-top: 10rpx; }
.chevron { color: #b2c0d2; font-size: 40rpx; margin-left: 16rpx; }
.panel { padding: 28rpx; margin-top: 24rpx; border: 1rpx solid #e3ebf5; border-radius: 18rpx; background-color: #f9fbff; }
.heading { font-size: 30rpx; color: #243e5c; font-weight: 700; line-height: 44rpx; }
.body { font-size: 25rpx; color: #536981; line-height: 42rpx; margin-top: 16rpx; }
.note { font-size: 22rpx; color: #8292a6; line-height: 36rpx; margin-top: 14rpx; }
.product-title { font-size: 29rpx; color: #214c83; font-weight: 700; margin-top: 24rpx; }
.demo, .status { color: #9a753d; font-size: 23rpx; line-height: 38rpx; margin-top: 16rpx; }
.label { color: #304b69; font-size: 26rpx; font-weight: 700; margin-top: 28rpx; }
.field-row { flex-direction: row; justify-content: space-between; padding: 22rpx 0; border-bottom: 1rpx solid #e8eef6; }
.field-label { flex: 1; font-size: 24rpx; color: #526880; }
.field-state { font-size: 24rpx; color: #aa7b37; margin-left: 16rpx; }
.notice { background-color: #eef6ff; border-radius: 16rpx; padding: 28rpx; margin-top: 32rpx; }
.notice-title { color: #2869b6; font-size: 27rpx; font-weight: 700; }
.notice-text { font-size: 23rpx; line-height: 39rpx; color: #6e87a4; margin-top: 14rpx; }
.primary, .home-button { margin-top: 32rpx; background-color: #0868f7; color: #fff; font-size: 27rpx; border-radius: 12rpx; }
.secondary { margin-top: 24rpx; background-color: #e7f0ff; color: #0868f7; font-size: 25rpx; border-radius: 12rpx; }
.link { color: #0868f7; font-size: 25rpx; margin-top: 24rpx; }
</style>