修复匹配详情中“用户自填未经核验”等来源说明,清理未参与项的橙色注意提示,修正贷款统计口径文案;补充公积金完整可选路径提取逻辑,确保明确无额外时长要求的公积金分支可正确识别并通过。
134 lines
11 KiB
Plaintext
134 lines
11 KiB
Plaintext
<template>
|
|
<view class="analyzing-page" :style="pageStyle()">
|
|
<view class="safe" :style="statusStyle(80)"></view>
|
|
<text class="analyzing-title">{{ title }}</text>
|
|
<text class="analyzing-sub">正式提交记录 · {{ assessmentId }}</text>
|
|
<view class="orbital"><view class="orbit orbit-one"></view><view class="orbit orbit-two"></view><view class="scan-ring" :style="scanRingStyle()"></view><view class="ai-core"><image src="/static/mobile/images/robot-cutout.png" mode="aspectFit" class="ai-core-image"></image></view></view>
|
|
<text class="status-message">{{ message }}</text>
|
|
<view v-if="processing" class="analysis-pulse"><view class="pulse-dot" :style="pulseDotStyle()"></view><text class="stage-label">{{ stageLabel }}</text><text class="pulse-dots" :style="pulseDotsStyle()">···</text></view>
|
|
<text v-if="error.length > 0" class="status-message">{{ error }}</text>
|
|
<text v-if="canRetry" class="view-now" @click="retry">{{ retrying ? '正在提交…' : '重新分析' }}</text>
|
|
<text v-if="succeeded" class="view-now" @click="report">查看匹配结果</text>
|
|
<text class="view-now" @click="refreshStatus">{{ retrying ? '正在提交重试…' : '刷新状态' }}</text>
|
|
<text class="view-now" @click="archive">查看已提交资料</text>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import { usePageLayout } from '@/services/layout.uts'
|
|
import { getSession } from '@/services/api.uts'
|
|
import { fetchAssessmentStatus, retryAssessment } from '@/packages/finance/services/records.uts'
|
|
import { reportRoute } from '@/services/report-route.uts'
|
|
const { statusStyle, pageStyle } = usePageLayout()
|
|
const title = ref('正在读取评估状态')
|
|
const message = ref(''); const error = ref(''); const assessmentId = ref('')
|
|
const publishedRevisionId = ref('')
|
|
const canRetry = ref(false); const succeeded = ref(false); const retrying = ref(false); const processing = ref(false); const stageLabel = ref('正在准备分析')
|
|
let retryKey = ''; let retryTask = ''; let retryOwner = ''
|
|
let timer = 0; let motionTimer = 0; let visible = false; let generation = 0
|
|
// 动效只反映服务端 processing 状态,不用视觉帧推断分析进度或完成时间。
|
|
const motionPhase = ref(0)
|
|
const phaseRadians = (): number => motionPhase.value * Math.PI * 2
|
|
const scanRingStyle = (): string => {
|
|
if (!processing.value) return 'opacity:0;transform:scale(.78);'
|
|
const phase = phaseRadians()
|
|
const progress = (Math.sin(phase) + 1) / 2
|
|
return 'opacity:' + (0.12 + progress * 0.62).toString() + ';transform:scale(' + (0.78 + progress * 0.52).toString() + ');'
|
|
}
|
|
const pulseDotStyle = (): string => 'opacity:' + (0.5 + ((Math.sin(phaseRadians()) + 1) / 2) * 0.5).toString() + ';transform:scale(' + (0.82 + ((Math.sin(phaseRadians()) + 1) / 2) * 0.26).toString() + ');'
|
|
const pulseDotsStyle = (): string => 'opacity:' + (0.25 + ((Math.sin(phaseRadians() - Math.PI / 2) + 1) / 2) * 0.75).toString() + ';'
|
|
const stopMotion = () => { if (motionTimer != 0) { clearInterval(motionTimer); motionTimer = 0 }; motionPhase.value = 0 }
|
|
const startMotion = () => {
|
|
stopMotion()
|
|
motionTimer = setInterval(() => { if (!processing.value) { stopMotion(); return }; motionPhase.value = (motionPhase.value + 0.035) % 1 }, 80)
|
|
}
|
|
async function loadStatus(): Promise<void> {
|
|
clearTimeout(timer)
|
|
if (assessmentId.value.length == 0) { title.value = '缺少评估记录'; message.value = '请从融资档案选择已提交的记录。'; return }
|
|
const session = getSession()
|
|
if (session == null) { uni.redirectTo({ url: '/packages/account/login/login' }); return }
|
|
const owner = session['token'] as string
|
|
const request = ++generation
|
|
error.value = ''
|
|
try {
|
|
const result = await fetchAssessmentStatus(assessmentId.value)
|
|
if (!visible || request != generation || getSession()?.getString('token') != owner) return
|
|
canRetry.value = result.canRetry; succeeded.value = result.status == 'succeeded'
|
|
const nextProcessing = result.status == 'processing'
|
|
if (nextProcessing != processing.value) { processing.value = nextProcessing; if (nextProcessing) startMotion(); else stopMotion() }
|
|
stageLabel.value = result.stage == 'queued' ? '正在排队,分析任务即将开始' : '正在整理资料,生成分析报告'
|
|
if (retryTask != result.taskId || retryOwner != owner) { retryKey = ''; retryTask = result.taskId; retryOwner = owner }
|
|
title.value = result.statusLabel
|
|
message.value = result.message.length > 0 ? result.message : (result.status == 'processing' ? (result.stage == 'queued' ? '资料已保存,等待分析。您可以稍后从档案查看。' : '正在分析自填资料,您可以稍后从档案查看。') : (result.status == 'failed' ? '分析未完成,可重新分析或刷新状态。已提交的资料无需重新填写。' : '分析已完成,可查看本次报告。'))
|
|
if (result.status == 'succeeded') {
|
|
// 只打开本次已发布版本;重评处理中或旧报告回包不会触发跳转。
|
|
const target = result.hasReport && result.publishedRevisionId == result.revisionId ? reportRoute(assessmentId.value, 'matches', result.publishedRevisionId) : ''
|
|
succeeded.value = target.length > 0
|
|
if (target.length > 0) { publishedRevisionId.value = result.publishedRevisionId; uni.redirectTo({ url: target }); return }
|
|
message.value = '本次报告尚未就绪,请刷新状态。'
|
|
}
|
|
// 只轮询服务端仍在处理的任务,不用计时器模拟完成。
|
|
if (result.status == 'processing') timer = setTimeout(() => { loadStatus() }, 5000)
|
|
} catch (err) {
|
|
if (visible && request == generation && getSession()?.getString('token') == owner) {
|
|
// 查询失败不能沿用旧任务的重试/报告按钮,也不能把网络错误当成分析失败。
|
|
canRetry.value = false; succeeded.value = false; processing.value = false; stopMotion(); message.value = '暂时无法获取最新状态,请刷新状态。已提交的资料无需重新填写。'
|
|
title.value = '状态加载失败'; error.value = err instanceof Error ? err.message : '请稍后重试'
|
|
}
|
|
}
|
|
}
|
|
// 重试进行中不再开启并行状态请求,避免旧状态覆盖新回执。
|
|
const refreshStatus = () => { if (!retrying.value) loadStatus() }
|
|
const retry = async () => {
|
|
if (retrying.value || !canRetry.value) return
|
|
const session = getSession()
|
|
if (session == null) { loadStatus(); return }
|
|
const owner = session['token'] as string
|
|
if (retryOwner != owner) { retryKey = ''; retryOwner = owner }
|
|
// 网络失败保留当前键;状态中的任务ID变化后才为下次尝试生成新键。
|
|
if (retryKey.length == 0) retryKey = 'retry_' + Date.now().toString() + '_' + Math.random().toString(36).substring(2, 14)
|
|
retrying.value = true; error.value = ''; clearTimeout(timer)
|
|
const request = ++generation
|
|
try {
|
|
await retryAssessment(assessmentId.value, retryKey)
|
|
if (visible && request == generation && getSession()?.getString('token') == owner) { canRetry.value = false; await loadStatus() }
|
|
} catch (err) { if (visible && request == generation && getSession()?.getString('token') == owner) error.value = err instanceof Error ? err.message : '重新分析失败,请重试' }
|
|
finally { retrying.value = false }
|
|
}
|
|
const report = () => { const target = reportRoute(assessmentId.value, 'matches', publishedRevisionId.value); if (succeeded.value && target.length > 0) uni.redirectTo({ url: target }) }
|
|
const archive = () => { uni.reLaunch({ url: '/packages/finance/archive/archive?assessmentId=' + encodeURIComponent(assessmentId.value) }) }
|
|
onLoad((options) => { if (options != null) assessmentId.value = options['assessmentId'] ?? '' })
|
|
onShow(() => { visible = true; loadStatus() })
|
|
onHide(() => { visible = false; generation++; clearTimeout(timer); stopMotion(); canRetry.value = false; succeeded.value = false; processing.value = false; publishedRevisionId.value = ''; message.value = ''; error.value = '' })
|
|
onUnload(() => { visible = false; generation++; clearTimeout(timer); stopMotion() })
|
|
</script>
|
|
|
|
<style>
|
|
.analyzing-page { background: linear-gradient(160deg, #061c49, #002c75); align-items: center; color: #fff; }
|
|
.safe { }
|
|
.analyzing-title { font-size: 45rpx; font-weight: 800; }
|
|
.analyzing-sub { margin-top: 20rpx; color: #c6d9ff; font-size: 25rpx; }
|
|
.orbital { width: 400rpx; height: 400rpx; margin-top: 55rpx; align-items: center; justify-content: center; position: relative; }
|
|
.orbit { position: absolute; border-radius: 50%; border: 2rpx solid rgba(79, 176, 255, .46); }
|
|
.orbit-one { width: 350rpx; height: 350rpx; }
|
|
.orbit-two { width: 280rpx; height: 280rpx; border-color: rgba(57, 235, 255, .5); }
|
|
.ai-core { width: 220rpx; height: 220rpx; border-radius: 50%; background-color: rgba(10, 102, 247, .32); box-shadow: 0 0 70rpx rgba(0, 165, 255, .48); align-items: center; justify-content: center; }
|
|
.scan-ring { transition: transform 80ms linear, opacity 80ms linear; }
|
|
.ai-core-image { width: 185rpx; height: 185rpx; border-radius: 50%; }
|
|
.scan-ring { position: absolute; width: 210rpx; height: 210rpx; border: 2rpx solid rgba(70, 216, 255, .85); border-radius: 50%; opacity: 0; }
|
|
.analysis-list { margin-top: 30rpx; width: 390rpx; }
|
|
.analysis-item { flex-direction: row; align-items: center; margin-top: 23rpx; font-size: 26rpx; color: #d8e6ff; }
|
|
.analysis-label { font-size: 27rpx; color: #d8e6ff; }
|
|
.analysis-mark { font-size: 24rpx; color: #ffffff; }
|
|
.analysis-check { width: 38rpx; height: 38rpx; border-radius: 50%; border: 2rpx solid #4c73a7; align-items: center; justify-content: center; margin-right: 17rpx; }
|
|
.analysis-check.done { background-color: #158aff; border-color: #41d7ff; color: white; }
|
|
.status-message { margin: 32rpx; color: #d8e6ff; font-size: 28rpx; line-height: 44rpx; text-align: center; }
|
|
.analysis-pulse { flex-direction: row; align-items: center; justify-content: center; margin-top: -12rpx; min-height: 34rpx; }
|
|
.pulse-dot { width: 12rpx; height: 12rpx; border-radius: 50%; background-color: #43ddff; box-shadow: 0 0 16rpx rgba(67, 221, 255, .9); }
|
|
.stage-label { margin-left: 12rpx; color: #9fc8ff; font-size: 23rpx; }
|
|
.pulse-dots { width: 40rpx; margin-left: 2rpx; color: #63dcff; font-size: 24rpx; letter-spacing: 4rpx; }
|
|
.estimate { margin-top: 64rpx; color: #8faeda; font-size: 22rpx; }
|
|
.view-now { margin-top: 28rpx; color: #62c8ff; font-size: 23rpx; }
|
|
.pulse-dot, .pulse-dots { transition: transform 80ms linear, opacity 80ms linear; }
|
|
</style>
|