Files

288 lines
34 KiB
Plaintext
Raw Permalink Normal View History

2026-09-10 10:57:40 +08:00
<template>
<view class="page" :style="pageStyle()">
<view class="status-bar" :style="statusStyle(0)"></view>
<view class="report-header">
<view class="header-back" @click="back"><AppIcon name="chevron-right" :size="40" class="back-icon" /></view>
<view class="header-title-area" :style="reportTitleStyle()"><text class="header-title">我的融资报告</text></view>
<view v-if="report != null" class="header-export" :style="navigationActionStyle(96, 1)"><ReportExport :assessmentId="assessmentId" :revisionId="revisionId" :toolbar="true" @authrequired="login" @sessionchanged="loadReport" @statuschange="exportStatus" /></view>
</view>
<view class="tabs"><view v-for="item in tabs" :key="item.id" :class="tab == item.id ? 'tab active' : 'tab'" @click="switchTab(item.id)"><text :class="tab == item.id ? 'tab-label selected' : 'tab-label'">{{ item.label }}</text></view></view>
<scroll-view scroll-y :style="contentStyle(report == null || tab == 'matches' ? 178 : 314, false)">
<view class="report-content" :style="bottomStyle(24)">
<!-- 离页清空报告时只切换可见性,避免在页面销毁过程中重新挂载文本节点。 -->
<text v-show="report == null" class="eyebrow">评估记录 #{{ assessmentId.length > 0 ? assessmentId : '待选择' }}</text>
<text v-if="loading" class="body-text">正在读取已保存报告…</text>
<view v-if="error.length > 0" class="panel"><text class="section-title">报告暂不可读取</text><text class="body-text">{{ error }}</text><button v-if="routeError.length == 0" class="secondary" @click="loadReport">重新加载报告</button><text class="action" @click="history">前往融资档案</text></view>
<template v-if="report != null">
<text v-if="report!.isPreviousResult" class="revision-notice">当前查看第 {{ report!.revisionNo }} 版结果。最新提交{{ report!.currentStatus == 'processing' ? '正在评估,成功后更新结果。' : (report!.currentStatus == 'failed' ? '评估失败,已保留上次成功结果。' : '已有新版本,可从档案查看。') }}</text>
<text v-show="downloadStatus.length > 0" class="download-status">{{ downloadStatus }}</text>
<view v-if="report!.version == 1" class="legacy"><text class="report-note">这是一份旧版自填报告,当时未采集融资需求、未计算完整度与准备状态,也未执行产品匹配。历史内容保持原样。</text></view>
<template v-if="tab == 'overview'">
<view :class="report!.financingGrade.grade.startsWith('A') || report!.financingGrade.grade.startsWith('B') ? 'readiness-band ready' : 'readiness-band attention'">
<text class="readiness-label">融资能力综合等级</text>
<view v-if="report!.financingGrade.status == 'evaluated'" class="grade-line"><text class="grade-code">{{ report!.financingGrade.grade }}</text><text class="grade-label">({{ report!.financingGrade.label }})</text></view>
<text v-else class="readiness-title">{{ report!.financingGrade.label }}</text>
<text v-if="report!.financingGrade.status != 'evaluated'" class="grade-caption">{{ report!.financingGrade.notice }}</text>
</view>
<view class="priority-section"><text class="section-title">{{ priorityItems.length > 0 ? '需重点完善的 ' + priorityItems.slice(0, 3).length.toString() + ' 项' : '资料核验与准备' }}</text><view v-for="(item, index) in priorityItems.slice(0, 3)" :key="item" class="priority-row"><text class="priority-number">{{ index + 1 }}</text><text class="priority-text">{{ item }}</text></view><text v-if="priorityItems.length == 0" class="report-note">本次暂无额外改善项,请按机构要求核实资料。</text><text v-if="priorityItems.length > 3" class="more-advice" @click="switchTab('improvements')">查看全部 {{ priorityItems.length }} 项改善建议 ›</text></view>
<!-- 首屏展示等级和编号改善项;完整度与所有冻结依据按需展开。 -->
<view class="details-toggle" @click="detailsOpen = !detailsOpen"><text class="details-label">查看完整报告</text><text class="details-action">{{ detailsOpen ? '收起' : '展开' }}</text></view>
<template v-if="detailsOpen">
<view v-if="report!.suggestions.length > 0" class="panel"><text class="section-title">改善建议与依据</text><text v-for="item in report!.suggestions" :key="item" class="body-text">{{ item }}</text></view>
<view class="panel"><view class="panel-heading"><text class="section-title">资料完整度</text><text class="percent">{{ report!.completenessPercent == null ? '—' : report!.completenessPercent!.toString() + '%' }}</text></view><view v-if="report!.completenessPercent != null" class="progress-track"><view class="progress-fill" :style="'width:' + report!.completenessPercent!.toString() + '%;'"></view></view><text class="report-note">{{ report!.completenessText }}</text><text class="report-note">完整度不代表信用评分、资料真实性或获批概率。</text></view>
<view v-if="report!.demand.length > 0" class="panel"><text class="section-title">本次资金需求</text><view class="fact-row" v-for="fact in report!.demand" :key="fact.label"><text class="fact-label">{{ fact.label }}</text><text class="fact-value">{{ fact.value }}</text></view></view>
<view class="panel"><text class="section-title">综合评分依据</text><template v-if="report!.financingGrade.status == 'evaluated'"><text class="body-text">融资能力 {{ report!.financingGrade.capacityScore }} / 100 · 资料完整度 {{ report!.financingGrade.completenessPercent }}%</text><text class="report-note">加权原始分 {{ report!.financingGrade.weightedScore }} · 最终分 {{ report!.financingGrade.score }}</text><view class="grade-dimension" v-for="item in report!.financingGrade.dimensions" :key="item.key"><view class="panel-heading"><text class="body-text">{{ item.label }}</text><text class="dimension-score">{{ item.score }} / {{ item.maximum }}</text></view><text class="report-note">{{ item.basis }}</text></view></template><text v-for="item in report!.financingGrade.limitations" :key="item" class="body-text">{{ item }}</text><text class="report-note">{{ report!.financingGrade.notice }}</text></view>
<view v-if="report!.version == 2" class="panel"><text class="section-title">资料准备状态:{{ report!.readinessLabel }}</text><text v-if="report!.readinessBasis.length == 0" class="report-note">本次没有可展示的准备状态依据。</text><text class="body-text" v-for="(item, index) in report!.readinessBasis" :key="index">{{ item }}</text><text class="report-note">资料完整度反映填写情况,达到100%仍需核对机构要求。</text></view>
<view class="panel"><text class="section-title">{{ report!.version == 2 ? '主要优势' : '资料解读' }}</text><text v-if="report!.observations.length == 0" class="report-note">暂无有充分依据的优势说明。</text><text class="body-text" v-for="(item, index) in report!.observations" :key="index">{{ item }}</text></view>
<view class="panel"><view class="panel-heading" @click="factsOpen = !factsOpen"><text class="section-title">本次提交的事实</text><text class="toggle">{{ factsOpen ? '收起' : '展开' }}</text></view><text class="report-note">以下为本次提交的资料;未填写不等于零。</text><template v-if="factsOpen"><view class="fact-row" v-for="fact in report!.facts" :key="fact.label"><text class="fact-label">{{ fact.label }}</text><text class="fact-value">{{ fact.value }}</text></view><text v-if="report!.facts.length == 0" class="report-note">暂无可展示的事实。</text></template></view>
<view v-if="report!.version == 2" class="panel"><view class="panel-heading" @click="completenessOpen = !completenessOpen"><text class="section-title">逐项填写情况</text><text class="toggle">{{ completenessOpen ? '收起' : '展开' }}</text></view><template v-if="completenessOpen"><view class="fact-row" v-for="item in report!.completenessItems" :key="item.label"><text class="fact-label">{{ item.label }}</text><text class="fact-value">{{ item.value }}</text></view><text v-if="report!.completenessItems.length == 0" class="report-note">本次未评定逐项完整度。</text></template></view>
<view class="panel"><text class="section-title">{{ report!.version == 2 ? '风险与核验边界' : '待补充信息' }}</text><text class="body-text" v-for="(item, index) in report!.warnings" :key="'system-' + index">{{ item }}</text><text class="body-text" v-for="(item, index) in report!.dataGaps" :key="index">{{ item }}</text><text v-if="report!.warnings.length == 0 && report!.dataGaps.length == 0" class="report-note">本次暂无额外提示,仍需关注实际风险和机构要求。</text></view>
</template>
</template>
<template v-else-if="tab == 'matches'">
<view class="matching-summary"><text class="matching-title">{{ candidates.length > 0 ? '为你展示' + candidates.length.toString() + '款融资产品' : '本次产品匹配结果' }}</text><text class="matching-caption">根据本次资料匹配,进入产品查看具体条件与依据。</text></view>
<view v-if="report!.matching.status != 'completed'" class="panel"><text class="section-title">本次未执行产品匹配</text><text class="body-text">这与“已匹配但没有候选”不同。历史报告不会自动补算;需要采用新资料与当前产品条件时,请重新评估。</text></view>
<template v-else>
<text v-if="candidateLoading" class="body-text">正在读取匹配产品…</text>
<view v-if="candidateError.length > 0" class="panel"><text class="section-title">匹配产品读取失败</text><text class="body-text">{{ candidateError }}</text><button class="secondary" @click="loadCandidates">重新加载</button></view>
<view v-if="!candidateLoading && candidateError.length == 0 && candidates.length == 0" class="panel"><text class="section-title">本次暂无可展示的产品</text><text class="body-text">可重新评估,采用最新资料与产品条件进行匹配。</text></view>
<MatchProductCard v-for="item in candidates" :key="item.id" :item="item" @open="openProduct" />
<button v-if="candidates.length > 0" class="matching-more" @click="allMatches('all')">查看更多产品(共{{ matchingTotal }}款)</button>
</template>
</template>
<template v-else>
<text class="headline">改善建议</text><text class="intro">依据本次报告整理 · 改善不保证获批</text>
<view class="priority-section"><text class="section-title">{{ priorityItems.length > 0 ? '需重点完善的 ' + priorityItems.length.toString() + ' 项' : '资料核验与准备' }}</text><view v-for="(item, index) in priorityItems" :key="item" class="priority-row"><text class="priority-number">{{ index + 1 }}</text><text class="priority-text">{{ item }}</text></view><text v-if="priorityItems.length == 0" class="report-note">本次暂无额外改善项,请按机构要求核实资料。</text></view>
<view v-if="report!.suggestions.length > 0" class="panel"><view class="panel-heading" @click="detailsOpen = !detailsOpen"><text class="section-title">建议依据</text><text class="toggle">{{ detailsOpen ? '收起' : '展开' }}</text></view><template v-if="detailsOpen"><text v-for="item in report!.suggestions" :key="item" class="body-text">{{ item }}</text></template></view>
<view v-if="report!.version == 2" class="panel"><text class="section-title">优先补充与核实</text><text v-if="missingItems.length == 0" class="report-note">{{ report!.completenessPercent == null ? '本次未评定缺失字段,不能据此认为资料齐全。' : '本次字段清单没有待补充或格式待核实项,仍需准备核验材料。' }}</text><view class="fact-row" v-for="item in missingItems" :key="item.label"><text class="fact-label">{{ item.label }}</text><text class="fact-value">{{ item.value }}</text></view><text class="body-text" v-for="(item, index) in report!.readinessBasis" :key="index">{{ item }}</text></view>
<view v-if="report!.matching.status != 'completed'" class="panel"><text class="section-title">产品条件提示</text><text class="report-note">本次未执行产品匹配,没有可关联的产品改善依据。</text></view>
<template v-else><view v-for="group in adviceGroups" :key="group.id" class="panel"><text class="section-title">{{ group.label }}</text><text class="report-note">仅展示本组前3项产品的已有依据,可进入完整列表查看更多。</text><text v-if="group.loading" class="body-text">正在读取产品依据…</text><template v-if="group.error.length > 0"><text class="body-text">{{ group.error }}</text><button class="secondary" @click="loadAdvice(group.id)">重试{{ group.label }}</button></template><text v-if="!group.loading && group.error.length == 0 && group.items.length == 0" class="report-note">本组暂无产品依据。</text><view v-for="item in group.items" :key="item.id" class="advice-product"><text class="product-name" @click="openProduct(item.id)">{{ item.product.name }} ›</text><text class="source-note">评估时版本 v{{ item.product.version }}</text><view v-for="reason in adviceReasons(item)" :key="reason.id"><text class="body-text">{{ reason.text }}</text><text v-for="line in reason.evidence" :key="line" class="evidence">{{ line }}</text></view><text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }}</text></view><text class="action" @click="allMatches(group.id)">查看全部{{ group.label }} ›</text></view></template>
</template>
<view v-if="tab == 'matches'" class="matching-footer"><text class="report-link" @click="editAssessment">修改资料,重新评估 ›</text><text class="matching-disclaimer">匹配度仅供参考,不代表获批概率。</text></view>
<template v-else>
<view class="report-links"><text class="report-link" @click="nextStep">下一步行动 ›</text><text class="report-link" @click="consult">咨询AI ›</text></view>
<view class="details-toggle" @click="metadataOpen = !metadataOpen"><text class="metadata-toggle">报告信息</text><text class="details-action">{{ metadataOpen ? '收起' : '展开' }}</text></view>
<view v-if="metadataOpen" class="metadata"><text class="report-note">评估 #{{ assessmentId }} · 第 {{ report!.revisionNo }} 版 · {{ report!.generatedAt }}</text><text class="report-note">报告已保存至融资档案。导出不含身份信息,请妥善保管下载文件。</text><text class="disclaimer">{{ report!.disclaimer }}</text><view class="report-links"><text class="report-link" @click="start">发起新评估</text><text class="report-link" @click="history">历史评估</text></view></view>
<text v-show="!metadataOpen" class="disclaimer">本报告依据本次资料生成,结果仅供参考,不代表授信或获批。</text>
</template>
</template>
</view>
</scroll-view>
<view v-if="report != null && tab != 'matches'" class="report-footer" :style="bottomStyle(24)"><button class="reevaluate-button" @click="editAssessment">重新评估</button></view>
</view>
2026-09-10 10:57:40 +08:00
</template>
<script setup lang="uts">
import AppIcon from '@/components/AppIcon.uvue'
import ReportExport from '@/packages/finance/components/ReportExport.uvue'
import MatchProductCard from '@/packages/finance/components/MatchProductCard.uvue'
import { usePageLayout } from '@/services/layout.uts'
import { getSession } from '@/services/api.uts'
import { assistantRoute } from '@/packages/finance/services/assistant.uts'
import { clearAssessmentFlow } from '@/services/assessment-flow.uts'
import { AnalysisReport, AssessmentField, fetchAnalysisReport } from '@/packages/finance/services/records.uts'
import { MatchPage, MatchView, MatchReason, fetchMatchPage, validRecordId } from '@/packages/finance/services/matches.uts'
type ReportTab = { id: string, label: string }
type AdviceGroup = { id: string, label: string, loading: boolean, error: string, items: MatchView[] }
const { contentStyle, pageStyle, statusStyle, bottomStyle, navigationTitleStyle, navigationActionStyle } = usePageLayout()
const reportTitleStyle = (): string => {
// 报告标题使用对称左右边距,保持屏幕中心对齐并避开微信胶囊。
return navigationTitleStyle()
}
const tabs: ReportTab[] = [{ id: 'overview', label: '评估概况' }, { id: 'matches', label: '匹配结果' }, { id: 'improvements', label: '改善建议' }]
const revisionId = ref(''); const assessmentId = ref(''); const tab = ref('overview'); const loading = ref(false); const error = ref(''); const routeError = ref('')
const report = ref<AnalysisReport | null>(null); const factsOpen = ref(false); const completenessOpen = ref(false)
// 日期与导出说明按需展开,首屏优先呈现准备状态和当前栏目的内容。
const metadataOpen = ref(false)
const detailsOpen = ref(false); const downloadStatus = ref('')
const exportStatus = (message: string) => { downloadStatus.value = message }
const candidates = ref<MatchView[]>([]); const candidateLoading = ref(false); const candidateError = ref('')
const adviceGroups = ref<AdviceGroup[]>([])
let generation = 0; let visible = false
const login = () => { uni.redirectTo({ url: '/packages/account/login/login?resumeReport=1&assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&tab=' + tab.value }) }
const missingItems = computed((): AssessmentField[] => report.value == null ? [] : report.value!.completenessItems.filter((item: AssessmentField): boolean => ['待补充','需核实'].includes(item.value)))
const priorityItems = computed((): string[] => report.value == null ? [] : report.value!.priorityItems)
const matchingTotal = computed((): string => report.value == null || report.value!.matching.status != 'completed' ? '0' : (parseInt(report.value!.matching.eligible) + parseInt(report.value!.matching.unknown) + parseInt(report.value!.matching.ineligible)).toString())
const adviceReasons = (item: MatchView): MatchReason[] => item.state == 'ineligible' ? item.blocking.concat(item.cautions) : item.cautions
const reset = () => { report.value = null; candidates.value = []; adviceGroups.value = []; candidateLoading.value = false; candidateError.value = ''; factsOpen.value = false; completenessOpen.value = false; metadataOpen.value = false; detailsOpen.value = false; downloadStatus.value = '' }
const current = (request: number, token: string): boolean => visible && request == generation && getSession()?.getString('token') == token
const sessionChanged = (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 verifyMatches = (page: MatchPage) => {
const value = report.value
if (value == null || page.assessmentId != assessmentId.value || page.revisionId != value.revisionId || page.resultVersion != value.version || page.status != 'completed' || page.snapshotId != value.matching.snapshotId || page.ruleVersion != value.matching.ruleVersion || page.orderingVersion != value.matching.orderingVersion || page.eligible != value.matching.eligible || page.ineligible != value.matching.ineligible || page.unknown != value.matching.unknown) throw new Error('产品结果与本报告的冻结版本不一致,请重新加载报告。')
}
const loadCandidates = async () => {
if (candidateLoading.value || report.value?.matching.status != 'completed') return
const request = generation; const token = getSession()?.getString('token') ?? ''
candidateLoading.value = true; candidateError.value = ''; candidates.value = []
try {
const result = await fetchMatchPage(assessmentId.value, 'all', '', revisionId.value)
if (!current(request, token)) { sessionChanged(request, token); return }
verifyMatches(result); candidates.value = result.items
} catch (err) { if (!sessionChanged(request, token)) candidateError.value = err instanceof Error ? err.message : '候选读取失败' }
finally { if (request == generation) candidateLoading.value = false }
}
const loadAdvice = async (id: string) => {
const group = adviceGroups.value.find((item: AdviceGroup): boolean => item.id == id)
if (group == null || group.loading || report.value?.matching.status != 'completed') return
const request = generation; const token = getSession()?.getString('token') ?? ''
group.loading = true; group.error = ''; group.items = []
try {
const result = await fetchMatchPage(assessmentId.value, id, '', revisionId.value)
if (!current(request, token)) { sessionChanged(request, token); return }
verifyMatches(result); group.items = result.items
} catch (err) { if (!sessionChanged(request, token)) group.error = err instanceof Error ? err.message : '产品依据读取失败' }
finally { if (request == generation) group.loading = false }
}
const loadReport = async () => {
const request = ++generation; reset(); error.value = ''; loading.value = false
if (routeError.value.length > 0) { error.value = routeError.value; return }
const session = getSession()
if (session == null) { login(); return }
const token = session.getString('token') ?? ''; loading.value = true
try {
const result = await fetchAnalysisReport(assessmentId.value, revisionId.value)
if (!current(request, token)) { sessionChanged(request, token); return }
report.value = result; revisionId.value = result.revisionId
if (tab.value == 'matches') loadCandidates()
if (tab.value == 'improvements') {
adviceGroups.value = [{ id: 'ineligible', label: '不符条件', loading: false, error: '', items: [] }, { id: 'unknown', label: '待核实项', loading: false, error: '', items: [] }]
if (result.matching.status == 'completed') { loadAdvice('ineligible'); loadAdvice('unknown') }
}
} catch (err) { if (!sessionChanged(request, token)) error.value = err instanceof Error ? err.message : '报告加载失败,请重试' }
finally { if (request == generation) loading.value = false }
}
// 切栏替换当前路由,刷新恢复所选栏目;只读取已保存快照,不提交评估或触发模型。
const switchTab = (value: string) => { if (value != tab.value && routeError.value.length == 0) uni.redirectTo({ url: '/packages/finance/report/report?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&tab=' + value }) }
const editAssessment = () => { uni.navigateTo({ url: '/packages/finance/assessment/assessment?editId=' + assessmentId.value }) }
const history = () => { uni.navigateTo({ url: '/packages/finance/archive/archive' }) }
const consult = () => { uni.navigateTo({ url: assistantRoute(assessmentId.value, [], 'report', '', revisionId.value) }) }
const nextStep = () => { if (report.value != null) uni.navigateTo({ url: '/packages/finance/next-step/next-step?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&section=actions' }) }
const back = () => { if (getCurrentPages().length > 1) uni.navigateBack(); else uni.redirectTo({ url: '/packages/finance/archive/archive' }) }
const allMatches = (group: string) => { uni.navigateTo({ url: '/packages/finance/matches/matches?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&group=' + group }) }
const openProduct = (id: string) => { uni.navigateTo({ url: '/packages/finance/product/product?assessmentId=' + assessmentId.value + (revisionId.value.length > 0 ? '&revisionId=' + revisionId.value : '') + '&productId=' + id + '&tab=explanation&from=report' }) }
const start = () => {
const token = getSession()?.getString('token') ?? ''; const request = generation
uni.showActionSheet({ itemList: ['个人评估','企业评估'], success: (result) => {
if (!current(request, token)) return
clearAssessmentFlow()
uni.navigateTo({ url: '/packages/finance/assessment/assessment?type=' + (result.tapIndex == 1 ? 'enterprise' : 'personal') })
} })
}
onLoad((options) => {
revisionId.value = options?.['revisionId'] ?? '';
if (revisionId.value.length > 0 && !validRecordId(revisionId.value)) routeError.value = '版本编号无效,请返回重新选择。'
assessmentId.value = options?.['assessmentId'] ?? ''; tab.value = options?.['tab'] ?? 'overview'
if (!validRecordId(assessmentId.value)) routeError.value = '请从融资档案选择有效的评估记录。'
if (!['overview','matches','improvements'].includes(tab.value)) routeError.value = '报告栏目参数无效,请从融资档案重新进入。'
})
onShow(() => { visible = true; loadReport() })
// 离页丢弃个人数据;旧请求不得覆盖新页面或新登录账户。
onHide(() => { visible = false; generation++; reset(); loading.value = false; error.value = '' })
onUnload(() => { visible = false; generation++; reset() })
2026-09-10 10:57:40 +08:00
</script>
<style>
.page { background-color: #fff; }
.status-bar { background-color: #fff; }
.report-header { height: 96rpx; padding: 0 28rpx; flex-direction: row; align-items: center; justify-content: space-between; position: relative; background-color: #fff; }
.header-back { width: 64rpx; height: 96rpx; justify-content: center; align-items: flex-start; }
.back-icon { transform: rotate(180deg); }
.header-title-area { position: absolute; left: 140rpx; right: 140rpx; top: 0; bottom: 0; align-items: center; justify-content: center; }
.header-title { color: #17243b; font-size: 32rpx; font-weight: 700; }
.header-export { align-items: center; justify-content: center; }
/* #ifdef MP-WEIXIN */
.header-title { font-size: 27rpx; }
/* #endif */
.tabs { height: 82rpx; flex-direction: row; background-color: #fff; padding: 0 28rpx; border-bottom: 1rpx solid #eff2f6; }
.tab { flex: 1; align-items: center; justify-content: center; }
.active { border-bottom: 4rpx solid #0868f7; }
.tab-label { font-size: 26rpx; color: #737e90; }
.selected { color: #0868f7; font-weight: 700; }
.revision-notice { font-size: 24rpx; line-height: 38rpx; color: #886934; padding: 18rpx 22rpx; background-color: #fff8eb; border-radius: 12rpx; margin-bottom: 12rpx; }
.download-status { font-size: 23rpx; line-height: 36rpx; color: #547292; padding-bottom: 18rpx; }
.report-content { padding: 28rpx 30rpx 24rpx; }
.matching-summary { background-color: #edf5ff; padding: 25rpx 26rpx; border-radius: 10rpx; margin-bottom: 8rpx; }
.matching-title { color: #244f85; font-size: 30rpx; line-height: 44rpx; font-weight: 600; }
.matching-caption { color: #7188a4; font-size: 22rpx; line-height: 35rpx; margin-top: 8rpx; }
.matching-more { background-color: #0868f7; color: #fff; height: 76rpx; line-height: 76rpx; font-size: 25rpx; border-radius: 10rpx; margin: 26rpx 0 0; }
.matching-footer { align-items: center; padding-top: 22rpx; }
.matching-disclaimer { font-size: 21rpx; line-height: 34rpx; color: #8c98a8; margin-top: 20rpx; text-align: center; }
.eyebrow { color: #778ba3; font-size: 22rpx; }
.headline { color: #1a3859; font-size: 34rpx; font-weight: 700; margin-top: 22rpx; }
.intro { color: #73869d; font-size: 24rpx; line-height: 40rpx; margin-top: 14rpx; }
.metadata-toggle { color: #8893a2; font-size: 22rpx; line-height: 36rpx; }
.metadata { padding: 0 0 12rpx; }
.readiness-band { padding: 28rpx 30rpx; min-height: 154rpx; background-color: #eef9f3; }
.attention { background-color: #fff8ed; }
.readiness-label { color: #526272; font-size: 25rpx; line-height: 38rpx; }
.grade-line { flex-direction: row; align-items: center; flex-wrap: wrap; margin-top: 8rpx; }
.grade-code { font-size: 76rpx; line-height: 90rpx; font-weight: 700; color: #179363; }
.grade-label { font-size: 36rpx; font-weight: 600; color: #179363; }
.grade-caption { font-size: 22rpx; line-height: 34rpx; color: #687b70; margin-top: 8rpx; }
.attention .grade-code { color: #a57925; }
.attention .grade-label { color: #a57925; }
.grade-dimension { padding: 16rpx 0; border-bottom: 1rpx solid #edf1f5; }
.dimension-score { font-size: 25rpx; line-height: 38rpx; color: #2763ad; }
.readiness-title { color: #17a46b; font-size: 46rpx; line-height: 64rpx; font-weight: 700; margin-top: 9rpx; }
.attention .readiness-title { color: #a57925; }
.priority-section { margin-top: 30rpx; }
.priority-row { flex-direction: row; align-items: flex-start; padding: 24rpx 0; border-bottom: 1rpx solid #edf0f4; min-height: 88rpx; }
.priority-number { width: 36rpx; height: 36rpx; border-radius: 18rpx; background-color: #f66f4e; text-align: center; color: #fff; font-size: 22rpx; line-height: 36rpx; margin-right: 18rpx; margin-top: 2rpx; flex-shrink: 0; }
.priority-text { flex: 1; font-size: 26rpx; line-height: 40rpx; color: #485568; }
.more-advice { font-size: 23rpx; line-height: 38rpx; color: #0868f7; margin-top: 18rpx; }
.details-toggle { flex-direction: row; align-items: center; justify-content: space-between; padding: 22rpx 0; border-bottom: 1rpx solid #edf0f4; }
.details-label { font-size: 24rpx; color: #738094; }
.details-action { font-size: 23rpx; color: #8794a5; }
.report-links { flex-direction: row; align-items: center; justify-content: space-between; margin-top: 22rpx; }
.report-link { font-size: 24rpx; line-height: 38rpx; color: #4e77aa; padding: 8rpx 0; }
.report-footer { position: fixed; left: 0; right: 0; bottom: 0; padding: 20rpx 30rpx 24rpx; background-color: #fff; }
.reevaluate-button { width: 100%; height: 92rpx; line-height: 92rpx; font-size: 28rpx; font-weight: 600; border-radius: 14rpx; background-color: #0868f7; color: #fff; margin: 0; }
.percent { color: #285c9d; font-size: 30rpx; font-weight: 700; }
.progress-track { height: 8rpx; border-radius: 4rpx; background-color: #dbe7f8; margin-top: 12rpx; overflow: hidden; }
.progress-fill { height: 8rpx; background-color: #4c8beb; border-radius: 4rpx; }
.panel { margin-top: 28rpx; padding: 0 0 24rpx; border-bottom: 1rpx solid #edf0f4; }
.panel-heading { flex-direction: row; justify-content: space-between; align-items: center; }
.section-title { color: #233750; font-size: 28rpx; font-weight: 700; line-height: 42rpx; }
.toggle { font-size: 24rpx; color: #0868f7; padding: 10rpx; }
.body-text { margin-top: 20rpx; font-size: 26rpx; color: #425b78; line-height: 44rpx; }
.report-note { font-size: 23rpx; line-height: 38rpx; color: #78889e; margin-top: 12rpx; }
.source-note { color: #8796a8; font-size: 22rpx; line-height: 36rpx; margin-top: 16rpx; }
.fact-row { flex-direction: row; padding: 22rpx 0; border-bottom: 1rpx solid #edf1f6; }
.fact-label { flex: 1; font-size: 25rpx; line-height: 39rpx; color: #738198; }
.fact-value { flex: 1; text-align: right; font-size: 25rpx; line-height: 39rpx; color: #233e5e; }
.legacy { background-color: #fff5e7; padding: 16rpx 22rpx 24rpx; border-radius: 14rpx; margin-top: 20rpx; }
.stats { flex-direction: row; padding: 26rpx 8rpx; border-top: 1rpx solid #edf0f4; border-bottom: 1rpx solid #edf0f4; margin-top: 26rpx; }
.stat { flex: 1; align-items: center; }
.stat-number { font-size: 39rpx; color: #1b5ebd; font-weight: 700; }
.stat-label { font-size: 22rpx; color: #5c7799; margin-top: 12rpx; }
.product-card { padding: 28rpx; border-radius: 14rpx; background-color: #fff; border: 1rpx solid #e0eaf7; margin-top: 24rpx; }
.product-name { flex: 1; color: #284566; font-size: 28rpx; line-height: 42rpx; font-weight: 700; }
.version { color: #8b9aab; font-size: 22rpx; margin-left: 14rpx; }
.demo { color: #9b762d; font-size: 22rpx; margin-top: 12rpx; }
.amount { color: #164b86; font-size: 38rpx; font-weight: 700; margin-top: 22rpx; }
.metrics { flex-direction: row; justify-content: space-between; margin-top: 16rpx; }
.metric { color: #5c7491; font-size: 23rpx; line-height: 37rpx; }
.reason { color: #6e8097; font-size: 23rpx; line-height: 38rpx; margin-top: 14rpx; }
.availability { color: #a07436; font-size: 23rpx; line-height: 38rpx; margin-top: 16rpx; }
.action { padding: 24rpx 0; color: #0868f7; font-size: 25rpx; }
.links { flex-direction: row; justify-content: space-between; }
.advice-product { border-top: 1rpx solid #e8eef6; padding-top: 24rpx; margin-top: 24rpx; }
.evidence { color: #8494a8; font-size: 22rpx; line-height: 36rpx; margin-top: 12rpx; }
.primary { background-color: #0868f7; color: #fff; font-size: 27rpx; border-radius: 12rpx; margin-top: 24rpx; }
.secondary { background-color: #eaf3ff; color: #0868f7; font-size: 25rpx; border-radius: 12rpx; margin-top: 22rpx; }
.disclaimer { color: #939caa; font-size: 21rpx; line-height: 34rpx; margin-top: 18rpx; }
/* #ifndef APP */
@media (min-width: 768px) {
.report-content { max-width: 720px; margin: 0 auto; }
.report-footer { align-items: center; }
.reevaluate-button { max-width: 690px; }
}
/* #endif */
2026-09-10 10:57:40 +08:00
</style>