Files

182 lines
18 KiB
Plaintext
Raw Permalink Normal View History

<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' ? '企业' : '个人' }}评估 · 第 {{ report!.revisionNo }} 版报告</text>
<text v-if="report!.isPreviousResult" class="revision-notice">当前查看第 {{ report!.revisionNo }} 版结果。最新提交{{ report!.currentStatus == 'processing' ? '正在评估,成功后更新结果。' : (report!.currentStatus == 'failed' ? '评估失败,已保留上次成功结果。' : '已有新版本,可从档案查看。') }}</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">{{ report!.matching.status != 'completed' ? '查看准备说明' : (productId.length > 0 && !canPrepare ? '核对产品条件' : '准备申请') }}</text><text class="action-note">{{ report!.matching.status != 'completed' ? '本报告没有产品匹配结果,可查看准备说明' : (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">{{ report!.matching.status == 'completed' ? '请先选择本次匹配中的产品,再查看对应材料。本页不会自动替您选择产品。' : '本报告没有可读取的产品匹配结果,暂不提供产品材料。您仍可查看报告、优化资料或咨询AI。' }}</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 class="status">{{ 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">修改资料并重新评估</button>
</view>
<view class="notice"><text class="notice-title">重要提示</text><text class="notice-text">本页仅提供材料准备与办理引导,不向银行或其他机构提交申请。匹配结果基于自填资料,不代表授信或获批承诺;实际产品条件、材料及办理方式以机构核实为准。</text></view>
<ReportExport :assessmentId="assessmentId" :revisionId="revisionId" @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 revisionId = ref(''); 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 + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.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.hasReport || !['personal', 'enterprise'].includes(status.type)) throw new Error('请从本人已成功生成的报告进入下一步行动。')
const result = await fetchAnalysisReport(assessmentId.value, revisionId.value)
if (stale(request, token)) return
report.value = result; revisionId.value = result.revisionId; 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, revisionId.value)
if (stale(request, token)) return
// 材料必须来自同次评估冻结产品,不允许用公共或其他报告产品补齐。
if (detail.assessmentId != assessmentId.value || detail.revisionId != result.revisionId || 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 + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') }) }
const chooseProduct = () => { if (ready() && report.value!.matching.status == 'completed') uni.navigateTo({ url: '/packages/finance/matches/matches?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&group=eligible' }) }
const prepare = () => {
if (!ready()) return
if (section.value == 'prepare') { load(); return }
uni.redirectTo({ url: nextStepRoute(assessmentId.value, productId.value, 'prepare', revisionId.value) })
}
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', '', revisionId.value) })
}
const restart = () => {
if (!ready() || !improveOpen.value) return
clearAssessmentFlow()
uni.navigateTo({ url: '/packages/finance/assessment/assessment?editId=' + assessmentId.value })
}
const back = () => {
if (getCurrentPages().length > 1) uni.navigateBack()
else if (validRecordId(assessmentId.value)) uni.redirectTo({ url: '/packages/finance/report/report?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') })
else history()
}
onLoad((options) => {
revisionId.value = options?.['revisionId'] ?? '';
if (revisionId.value.length > 0 && !validRecordId(revisionId.value)) routeError.value = '版本编号无效,请返回重新选择。'
assessmentId.value = options?.['assessmentId'] ?? ''; productId.value = options?.['productId'] ?? ''; section.value = options?.['section'] ?? 'actions'
if (nextStepRoute(assessmentId.value, productId.value, section.value, revisionId.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; }
.revision-notice { font-size: 24rpx; color: #9a753d; line-height: 38rpx; margin-top: 16rpx; }
.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>