新增revisionId参数到路由、服务及导出接口,实现不同评估版本的隔离访问。删除已废弃的需求相关服务文件,移除取消采集的融资金额、期限和用途字段,更新相关页面提示文本。同时更新文档,说明评估版本、最新资料及产品展示规则的调整,支持前台正常业务名称显示,去除环境标签。
266 lines
23 KiB
Plaintext
266 lines
23 KiB
Plaintext
<template>
|
|
<view class="page" :style="pageStyle()">
|
|
<AppHeader title="我的融资档案" />
|
|
<scroll-view scroll-y :style="contentStyle(96, false)">
|
|
<view class="archive-content">
|
|
<view class="archive-switch">
|
|
<text :class="type == 'personal' ? 'switch-item switch-active' : 'switch-item'" @click="switchType('personal')">个人档案</text>
|
|
<text :class="type == 'enterprise' ? 'switch-item switch-active' : 'switch-item'" @click="switchType('enterprise')">企业档案</text>
|
|
</view>
|
|
|
|
<view v-if="detail != null || records.length > 0" class="record-picker">
|
|
<view class="record-picker-head" @click="historyOpen = !historyOpen">
|
|
<view class="record-caption"><text class="record-title">{{ detail == null ? '选择已提交的评估' : '评估记录 #' + detail!.record.id }}</text><text class="record-meta">{{ detail == null ? '按提交时间由近到远排列' : detail!.record.submittedAt + ' · ' + detail!.record.statusLabel }}</text></view>
|
|
<text class="history-link">{{ historyOpen ? '收起记录' : '切换记录' }}</text>
|
|
</view>
|
|
<view v-if="historyOpen" class="history-list">
|
|
<text class="history-note">新建评估独立保存;修改记录可在版本历史查看。</text>
|
|
<view v-for="item in records" :key="item.id" :class="item.id == selectedId ? 'history-row history-selected' : 'history-row'" @click="chooseRecord(item.id)">
|
|
<view class="record-caption"><text class="history-title">{{ item.submittedAt }} · #{{ item.id }}</text><text class="record-meta">{{ item.city }} · {{ item.statusLabel }}</text></view><text class="history-state">{{ item.id == selectedId ? '当前' : '查看' }}</text>
|
|
</view>
|
|
<text v-if="listError.length > 0" class="error-text">{{ listError }}</text>
|
|
<text v-if="listError.length > 0" class="text-action" @click="retryList">重新加载记录</text>
|
|
<text v-if="listLoading" class="history-note">正在读取历史记录…</text>
|
|
<text v-else-if="hasMore" class="text-action" @click="loadMore">加载更早记录</text>
|
|
<text v-else-if="listError.length == 0" class="history-note">已显示本类全部记录</text>
|
|
</view>
|
|
</view>
|
|
|
|
<view v-if="error.length > 0" class="notice"><text class="error-text">{{ error }}</text><text class="text-action" @click="refresh">重新加载</text><text v-if="selectedId.length > 0" class="text-action" @click="showLatest">选择其他历史记录</text></view>
|
|
<text v-if="loading" class="loading-text">正在读取已提交的档案…</text>
|
|
<view class="completion">
|
|
<view class="completion-head"><text class="completion-title">档案完整度</text><view class="score-wrap"><text class="completion-score">{{ completeness == null ? '—' : completeness!.toString() }}</text><text v-if="completeness != null" class="score-unit">%</text></view></view>
|
|
<view class="progress-track"><view class="progress-bar" :style="'width:' + (completeness ?? 0).toString() + '%;'"></view></view>
|
|
<text class="completion-desc">{{ completenessMessage }}</text>
|
|
<text v-if="completenessFailed" class="text-action completion-retry" @click="retryCompleteness">重新读取完整度</text>
|
|
</view>
|
|
|
|
<view v-if="!loading && error.length == 0 && detail == null" class="empty-panel"><text class="empty-title">暂无{{ type == 'personal' ? '个人' : '企业' }}评估档案</text><text class="empty-note">完成一次评估提交后,即可在这里查看资料与报告。</text></view>
|
|
<view class="archive-list">
|
|
<view v-for="section in sections" :key="section.id" class="section">
|
|
<view class="archive-row" @click="toggleSection(section.id)">
|
|
<AppIcon :name="section.icon" :size="48" />
|
|
<view class="archive-labels"><text class="archive-name">{{ section.title }}</text><text class="archive-note">{{ section.note }}</text></view>
|
|
<text :class="detail != null && section.id != 'authorization' ? 'state state-done' : 'state'">{{ sectionState(section) }}</text>
|
|
<AppIcon v-if="detail != null" name="chevron-right" :size="28" :class="expanded == section.id ? 'arrow-open' : ''" />
|
|
</view>
|
|
<view v-if="expanded == section.id && detail != null" class="section-detail">
|
|
<template v-if="section.id == 'authorization'"><text class="section-hint">私有资料上传与外部查询授权尚未开放,本次未通过这些服务上传资料或发起查询。</text></template>
|
|
<template v-else><text class="section-hint">以下为本次提交时的资料,仅供查看。</text><view v-for="field in section.fields" :key="field.label" class="detail-row"><text class="field-label">{{ field.label }}</text><text class="field-value">{{ field.value }}</text></view></template>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<view v-if="detail != null" class="record-picker">
|
|
<view class="record-picker-head"><text class="record-title">本评估版本历史</text></view>
|
|
<view v-for="item in versions" :key="item.revisionId" class="history-row" @click="chooseVersion(item.revisionId)"><text class="history-title">第 {{ item.revisionNo }} 版 · {{ item.submittedAt }} · {{ item.statusLabel }}</text><text class="history-state">{{ detail!.record.revisionId == item.revisionId ? '当前查看' : '查看' }}</text></view>
|
|
<text v-if="versionsError.length > 0" class="error-text">{{ versionsError }}</text><text v-if="versionsLoading" class="history-note">正在读取版本…</text><text v-else-if="versionsMore || versionsError.length > 0" class="text-action" @click="moreVersions">继续读取版本</text>
|
|
</view>
|
|
<button v-if="detail != null" class="new-action" @click="editAssessment">修改资料并重新评估</button>
|
|
<text v-if="detail != null && detail!.record.message.length > 0" class="task-note">{{ detail!.record.message }}</text>
|
|
<button v-if="detail != null" class="primary-button main-action" @click="openAnalysis">{{ detail!.record.status == 'succeeded' ? '查看分析报告' : '查看分析状态 / 重试' }}</button>
|
|
<button v-if="detail != null && detail!.record.status != 'succeeded' && detail!.record.hasReport" class="new-action" @click="openPreviousReport">查看上次成功结果</button>
|
|
<button :class="detail == null ? 'primary-button main-action' : 'new-action'" :disabled="loading" @click="start">发起新的{{ type == 'personal' ? '个人' : '企业' }}评估</button>
|
|
<text class="local-note">修改会在本评估下保存新版本;新建评估另建记录。
|
|
未提交的填写内容不会保存。</text>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
<script setup lang="uts">
|
|
import AppHeader from '@/components/AppHeader.uvue'
|
|
import AppIcon from '@/components/AppIcon.uvue'
|
|
import { usePageLayout } from '@/services/layout.uts'
|
|
import { getSession } from '@/services/api.uts'
|
|
import { clearAssessmentFlow } from '@/services/assessment-flow.uts'
|
|
import { AssessmentRecord, AssessmentDetail, ArchiveSection, fetchAssessments, fetchAssessmentDetail, fetchArchiveCompleteness, fetchAssessmentRevisions } from '@/packages/finance/services/records.uts'
|
|
const { pageStyle, contentStyle } = usePageLayout()
|
|
const type = ref('personal'); const selectedId = ref(''); const records = ref<AssessmentRecord[]>([])
|
|
const revisionId = ref(''); const versions = ref<AssessmentRecord[]>([]); const versionsMore = ref(false); const versionsError = ref(''); const versionsLoading = ref(false); let versionsCursor = ''
|
|
const detail = ref<AssessmentDetail | null>(null); const expanded = ref(''); const historyOpen = ref(false)
|
|
const loading = ref(false); const error = ref(''); const listError = ref(''); const listLoading = ref(false); const hasMore = ref(false)
|
|
const completeness = ref<number | null>(null); const completenessFailed = ref(false)
|
|
const completenessMessage = ref('提交评估后,查看对应报告的资料完整度。')
|
|
let cursor = ''; let generation = 0; let reportRequest = 0; let visible = false; let routeError = ''; let routeId = ''; let owner = ''
|
|
const sections = computed((): ArchiveSection[] => detail.value != null ? detail.value!.sections : [
|
|
{ id: 'basic', icon: 'file-text', title: '基本信息', note: type.value == 'enterprise' ? '企业与联系人信息' : '身份与联系方式', fields: [], provided: 0 },
|
|
{ id: 'income', icon: 'income', title: type.value == 'enterprise' ? '经营信息' : '职业与收入', note: type.value == 'enterprise' ? '经营行业、月数与营收' : '职业、收入与缴纳情况', fields: [], provided: 0 },
|
|
{ id: 'assets', icon: 'asset', title: '资产与负债', note: '贷款、信用卡额度与房车情况', fields: [], provided: 0 },
|
|
{ id: 'authorization', icon: 'authorization', title: '资料与授权', note: '私有资料与外部查询授权', fields: [], provided: 0 }
|
|
])
|
|
const sectionState = (section: ArchiveSection): string => {
|
|
if (section.id == 'authorization') return '未开放'
|
|
if (detail.value == null) return '暂无记录'
|
|
return section.provided > 0 ? '已记录' : '未填写'
|
|
}
|
|
const clearView = () => {
|
|
detail.value = null; records.value = []; versions.value = []; versionsMore.value = false; versionsError.value = ''; versionsCursor = ''; versionsLoading.value = false; cursor = ''; hasMore.value = false; expanded.value = ''
|
|
completeness.value = null; completenessFailed.value = false; reportRequest++
|
|
completenessMessage.value = '提交评估后,查看对应报告的资料完整度。'
|
|
error.value = ''; listError.value = ''; listLoading.value = false
|
|
}
|
|
const active = (request: number, token: string): boolean => {
|
|
if (!visible || request != generation) return false
|
|
const session = getSession()
|
|
if (session?.getString('token') == token) return true
|
|
// 包括报告/历史分页返回401的情况:立即移除已显示资料,不只忽略迟到响应。
|
|
generation++; clearView(); loading.value = false; selectedId.value = routeId
|
|
if (session == null) uni.redirectTo({ url: '/packages/account/login/login' })
|
|
else error.value = '登录状态已变化,请重新加载档案。'
|
|
return false
|
|
}
|
|
const readCompleteness = async (id: string, request: number, token: string) => {
|
|
const reportSequence = ++reportRequest
|
|
completeness.value = null; completenessFailed.value = false; completenessMessage.value = '正在读取本次报告的资料完整度…'
|
|
try {
|
|
const result = await fetchArchiveCompleteness(id, detail.value?.record.revisionId ?? revisionId.value)
|
|
if (active(request, token) && reportSequence == reportRequest) { completeness.value = result.percent; completenessMessage.value = result.description }
|
|
} catch (err) {
|
|
if (active(request, token) && reportSequence == reportRequest) {
|
|
completenessFailed.value = true; completenessMessage.value = '完整度暂时读取失败,已提交资料仍可查看。'
|
|
}
|
|
}
|
|
}
|
|
const readList = async (append: boolean, request: number, token: string) => {
|
|
listLoading.value = true; listError.value = ''
|
|
try {
|
|
const result = await fetchAssessments(append ? cursor : '', type.value)
|
|
if (!active(request, token)) return
|
|
const incoming = result.items.filter((item: AssessmentRecord): boolean => !append || !records.value.some((old: AssessmentRecord): boolean => old.id == item.id))
|
|
records.value = append ? records.value.concat(incoming) : incoming; hasMore.value = result.hasMore; cursor = result.nextCursor
|
|
} catch (err) {
|
|
if (active(request, token)) listError.value = err instanceof Error ? err.message : '历史记录读取失败,请重试'
|
|
} finally { if (active(request, token)) listLoading.value = false }
|
|
}
|
|
const readVersions = async (append: boolean, request: number, token: string) => {
|
|
versionsLoading.value = true; versionsError.value = ''
|
|
try {
|
|
const page = await fetchAssessmentRevisions(selectedId.value, append ? versionsCursor : '')
|
|
if (!active(request, token)) return
|
|
versions.value = append ? versions.value.concat(page.items) : page.items; versionsMore.value = page.hasMore; versionsCursor = page.nextCursor
|
|
} catch (err) { if (active(request, token)) versionsError.value = err instanceof Error ? err.message : '版本历史读取失败' }
|
|
finally { if (active(request, token)) versionsLoading.value = false }
|
|
}
|
|
const moreVersions = () => { if (!versionsLoading.value) readVersions(versions.value.length > 0, generation, owner) }
|
|
const chooseVersion = (id: string) => { uni.redirectTo({ url: '/packages/finance/archive/archive?assessmentId=' + selectedId.value + '&revisionId=' + id }) }
|
|
const editAssessment = () => { if (detail.value != null) uni.navigateTo({ url: '/packages/finance/assessment/assessment?editId=' + selectedId.value }) }
|
|
const refresh = async () => {
|
|
const request = ++generation; clearView(); loading.value = true
|
|
const session = getSession()
|
|
if (session == null) { loading.value = false; uni.redirectTo({ url: '/packages/account/login/login' }); return }
|
|
const token = session.getString('token') ?? ''; owner = token
|
|
if (routeError.length > 0) { error.value = routeError; loading.value = false; completenessMessage.value = '请先选择有效的评估记录。'; return }
|
|
try {
|
|
// 显式ID先校验本人详情;失败不能自动切到最新记录,掩盖越权或已删除状态。
|
|
if (selectedId.value.length > 0) {
|
|
const result = await fetchAssessmentDetail(selectedId.value, revisionId.value)
|
|
if (!active(request, token)) return
|
|
detail.value = result; type.value = result.record.type
|
|
await readList(false, request, token)
|
|
} else {
|
|
await readList(false, request, token)
|
|
if (!active(request, token)) return
|
|
if (listError.value.length > 0) throw new Error(listError.value)
|
|
if (records.value.length > 0) {
|
|
selectedId.value = records.value[0].id
|
|
const result = await fetchAssessmentDetail(selectedId.value, revisionId.value)
|
|
if (!active(request, token)) return
|
|
detail.value = result
|
|
}
|
|
}
|
|
if (!active(request, token)) return
|
|
if (listError.value.length > 0) historyOpen.value = true
|
|
loading.value = false
|
|
if (detail.value != null) {
|
|
await readVersions(false, request, token)
|
|
if (detail.value!.record.status == 'succeeded') await readCompleteness(detail.value!.record.id, request, token)
|
|
else completenessMessage.value = detail.value!.record.status == 'failed' ? '本次分析未完成,暂没有报告完整度;已提交资料已保留。' : '本次分析尚未完成,报告生成后可查看完整度。'
|
|
}
|
|
} catch (err) {
|
|
if (active(request, token)) { error.value = err instanceof Error ? err.message : '档案读取失败,请重试'; completenessMessage.value = '档案暂不可读取,请重新加载或选择其他记录。' }
|
|
} finally { if (active(request, token)) loading.value = false }
|
|
}
|
|
const retryList = () => { if (!listLoading.value) readList(false, generation, owner) }
|
|
const loadMore = () => { if (!listLoading.value && hasMore.value) readList(true, generation, owner) }
|
|
const retryCompleteness = () => { if (detail.value != null && detail.value!.record.status == 'succeeded') readCompleteness(detail.value!.record.id, generation, owner) }
|
|
const toggleSection = (id: string) => { if (detail.value != null) expanded.value = expanded.value == id ? '' : id }
|
|
// 类型与显式历史选择留在路由里,刷新重新鉴权后仍查看同一记录;不保存草稿或资料缓存。
|
|
const switchType = (next: string) => { if (next != type.value) uni.redirectTo({ url: '/packages/finance/archive/archive?type=' + next }) }
|
|
const chooseRecord = (id: string) => { if (id == selectedId.value) { historyOpen.value = false; return }; uni.redirectTo({ url: '/packages/finance/archive/archive?type=' + type.value + '&assessmentId=' + encodeURIComponent(id) }) }
|
|
const showLatest = () => { uni.redirectTo({ url: '/packages/finance/archive/archive?type=' + type.value }) }
|
|
const start = () => { clearAssessmentFlow(); uni.navigateTo({ url: '/packages/finance/assessment/assessment?type=' + type.value }) }
|
|
const openPreviousReport = () => { if (detail.value != null) uni.navigateTo({ url: '/packages/finance/report/report?assessmentId=' + selectedId.value + '&revisionId=' + detail.value!.record.publishedRevisionId }) }
|
|
const openAnalysis = () => {
|
|
if (detail.value == null) return
|
|
const page = detail.value!.record.status == 'succeeded' ? 'report/report' : 'analyzing/analyzing'
|
|
uni.navigateTo({ url: '/packages/finance/' + page + '?assessmentId=' + encodeURIComponent(detail.value!.record.id) + (page == 'report/report' ? '&revisionId=' + detail.value!.record.revisionId : '') })
|
|
}
|
|
onLoad((options) => {
|
|
revisionId.value = options?.['revisionId'] ?? ''
|
|
if (revisionId.value.length > 0 && !/^[1-9][0-9]{0,9}$/.test(revisionId.value)) routeError = '版本编号无效,请重新选择。'
|
|
type.value = options?.['type'] ?? 'personal'; routeId = options?.['assessmentId'] ?? ''; selectedId.value = routeId
|
|
if (!['personal', 'enterprise'].includes(type.value)) { type.value = 'personal'; routeError = '档案类型无效,请重新选择。' }
|
|
if (options?.['assessmentId'] != null && !/^[1-9][0-9]{0,9}$/.test(routeId)) routeError = '评估编号无效,请重新选择历史记录。'
|
|
})
|
|
onShow(() => {
|
|
visible = true
|
|
if (getSession()?.getString('token') != owner) { selectedId.value = routeId; historyOpen.value = false }
|
|
refresh()
|
|
})
|
|
// 离开即清空敏感内容;账户切换、快速换档和迟到响应均不能恢复旧账户资料。
|
|
onHide(() => { visible = false; generation++; clearView(); loading.value = false })
|
|
onUnload(() => { visible = false; generation++; clearView() })
|
|
</script>
|
|
<style>
|
|
.archive-content { padding: 26rpx 30rpx 70rpx; }
|
|
.archive-switch { flex-direction: row; background-color: #eaf0f8; border-radius: 16rpx; padding: 6rpx; margin-bottom: 24rpx; }
|
|
.switch-item { flex: 1; text-align: center; font-size: 27rpx; color: #708099; padding: 18rpx 0; border-radius: 12rpx; }
|
|
.switch-active { background-color: #fff; color: #0868f7; font-weight: 600; }
|
|
.record-picker { background-color: #fff; border: 1rpx solid #e7edf6; border-radius: 18rpx; margin-bottom: 24rpx; }
|
|
.record-picker-head { flex-direction: row; align-items: center; padding: 24rpx; }
|
|
.record-caption { flex: 1; }
|
|
.record-title { font-size: 26rpx; font-weight: 600; color: #23344e; }
|
|
.record-meta { margin-top: 9rpx; font-size: 22rpx; line-height: 32rpx; color: #7b899c; }
|
|
.history-link { color: #0868f7; font-size: 24rpx; margin-left: 16rpx; padding: 10rpx 0; }
|
|
.history-list { padding: 0 24rpx 16rpx; border-top: 1rpx solid #edf1f6; }
|
|
.history-note { color: #8a96a6; font-size: 22rpx; line-height: 34rpx; padding: 16rpx 0; }
|
|
.history-row { flex-direction: row; align-items: center; padding: 20rpx 12rpx; border-bottom: 1rpx solid #edf1f6; }
|
|
.history-selected { background-color: #f0f6ff; border-radius: 10rpx; }
|
|
.history-title { color: #354963; font-size: 23rpx; }
|
|
.history-state { color: #0868f7; font-size: 22rpx; margin-left: 12rpx; }
|
|
.completion { padding: 30rpx; border-radius: 20rpx; background-color: #edf5ff; }
|
|
.completion-head { flex-direction: row; justify-content: space-between; align-items: center; }
|
|
.completion-title { font-size: 30rpx; font-weight: 600; color: #23344e; }
|
|
.score-wrap { flex-direction: row; align-items: flex-end; }
|
|
.completion-score { font-size: 52rpx; font-weight: 700; color: #087af5; }
|
|
.score-unit { font-size: 26rpx; font-weight: 600; color: #087af5; }
|
|
.progress-track { height: 12rpx; border-radius: 6rpx; background-color: #dbe6f8; margin-top: 26rpx; overflow: hidden; }
|
|
.progress-bar { height: 12rpx; border-radius: 6rpx; background-color: #087af5; }
|
|
.completion-desc { font-size: 24rpx; line-height: 38rpx; color: #7d8b9d; margin-top: 20rpx; }
|
|
.completion-retry { padding-bottom: 0; }
|
|
.archive-list { background-color: #fff; border: 1rpx solid #e7edf6; border-radius: 20rpx; margin-top: 24rpx; padding: 0 24rpx; }
|
|
.section { border-bottom: 1rpx solid #f0f3f8; }
|
|
.archive-row { flex-direction: row; align-items: center; padding: 28rpx 0; }
|
|
.archive-labels { flex: 1; margin-left: 22rpx; margin-right: 12rpx; }
|
|
.archive-name { font-size: 29rpx; font-weight: 500; color: #23344e; }
|
|
.archive-note { font-size: 23rpx; line-height: 34rpx; color: #9099a5; margin-top: 7rpx; }
|
|
.state { font-size: 23rpx; color: #9aA2ac; margin-right: 8rpx; }
|
|
.state-done { color: #65839f; }
|
|
.arrow-open { transform: rotate(90deg); }
|
|
.section-detail { padding: 0 2rpx 24rpx; }
|
|
.section-hint { font-size: 23rpx; line-height: 38rpx; color: #7b899c; }
|
|
.detail-row { flex-direction: row; padding: 20rpx 0; border-bottom: 1rpx solid #f2f5f9; }
|
|
.field-label { width: 210rpx; font-size: 24rpx; line-height: 38rpx; color: #7b899c; }
|
|
.field-value { flex: 1; text-align: right; font-size: 25rpx; line-height: 38rpx; color: #23344e; }
|
|
.main-action { margin-top: 30rpx; font-size: 29rpx; }
|
|
.new-action { margin-top: 20rpx; padding: 24rpx; border-radius: 16rpx; background-color: #eaf3ff; color: #0868f7; font-size: 27rpx; text-align: center; }
|
|
.local-note { font-size: 22rpx; line-height: 36rpx; color: #99a2af; margin-top: 22rpx; text-align: center; }
|
|
.empty-panel { padding: 24rpx 8rpx 0; }
|
|
.empty-title { font-size: 27rpx; color: #435874; }
|
|
.empty-note { font-size: 23rpx; line-height: 36rpx; color: #8592a4; margin-top: 10rpx; }
|
|
.notice { padding: 24rpx; margin-bottom: 24rpx; background-color: #fff; border-radius: 16rpx; }
|
|
.error-text { color: #a7533e; font-size: 25rpx; line-height: 38rpx; }
|
|
.text-action { color: #0868f7; font-size: 24rpx; padding: 18rpx 0; }
|
|
.loading-text { color: #7b899c; font-size: 24rpx; padding: 0 0 24rpx; }
|
|
.task-note { font-size: 24rpx; line-height: 38rpx; color: #7b899c; margin-top: 22rpx; }
|
|
</style>
|