feat(融资): 新增产品对比页面及相关接口实现
新增产品对比页面 compare/compare,支持同一评估下最多3个产品的横向对比,展示额度、期限、利率、适用条件等核心指标及匹配详情。 新增 matches.uts 服务层,封装匹配列表、详情、公共产品查询接口。 新增 account 分包的 latest-report.uts 服务,用于查找用户最新成功报告。 调整个人中心页面,将“评估记录”改为“开始评估”,新增最近报告直达功能。 更新文档,标记匹配相关接口及原型07、09页面已实现,调整页面数量至26页。 移除 finance 分包中旧的 fetchMatches、fetchProduct 模拟数据接口,统一使用正式API。
This commit is contained in:
@@ -1,25 +1,55 @@
|
||||
<template>
|
||||
<view class="page" :style="pageStyle()">
|
||||
<AppHeader title="融资资料分析报告" />
|
||||
<scroll-view scroll-y :style="contentStyle(0, false)">
|
||||
<AppHeader title="我的融资报告" :customBack="true" @back="back" />
|
||||
<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(186, false)">
|
||||
<view class="report-content">
|
||||
<text class="report-note">评估记录 · {{ assessmentId }}</text>
|
||||
<text v-if="loading" class="body-text">正在加载报告…</text>
|
||||
<view v-if="error.length > 0" class="panel"><text class="body-text">{{ error }}</text><text class="action" @click="loadReport">重新加载</text><text class="action" @click="history">前往融资档案</text></view>
|
||||
<text 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 class="report-note">分析模型:{{ report!.modelCode }}</text>
|
||||
<template v-if="report!.version == 2">
|
||||
<view 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><text class="readiness-value">{{ report!.completenessText }}</text><text class="report-note">仅统计当前适用的自填字段,不代表信用评分或真实性。</text><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></view>
|
||||
<view class="panel"><text class="section-title">融资准备状态</text><text class="readiness-value">{{ report!.readinessLabel }}</text><text class="body-text" v-for="(item, index) in report!.readinessBasis" :key="index">{{ item }}</text><text class="report-note">资料未经核验;即使完整度达到100%,仍需核对机构要求的材料。</text></view>
|
||||
<text class="report-note">生成于 {{ report!.generatedAt }} · 用户自填资料分析</text>
|
||||
<view v-if="report!.version == 1" class="legacy"><text class="report-note">这是一份旧版自填报告,当时未采集融资需求、未计算完整度与准备状态,也未执行产品匹配。历史内容保持原样。</text></view>
|
||||
<template v-if="tab == 'overview'">
|
||||
<view class="hero">
|
||||
<text class="hero-label">本次融资准备状态</text><text class="hero-title">{{ report!.readinessLabel }}</text>
|
||||
<text class="hero-note">{{ report!.version == 1 ? '旧版未评定,可重新填写并发起新评估' : '按本次提交资料及评估时规则生成' }}</text>
|
||||
<view class="hero-divider"></view><view class="panel-heading"><text class="hero-label">资料完整度</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="hero-note">{{ report!.completenessText }}</text><text class="hero-note">完整度不代表信用评分、资料真实性或获批概率。</text>
|
||||
</view>
|
||||
<view v-if="report!.version == 2" 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><text class="body-text">{{ report!.summary }}</text><text class="source-note">分析来源:已保存的AI文本 · {{ report!.modelCode }}</text></view>
|
||||
<view v-if="report!.version == 2" class="panel"><text class="section-title">准备状态依据</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>
|
||||
<button class="primary" @click="switchTab('matches')">查看本次匹配与依据</button>
|
||||
</template>
|
||||
<view class="panel"><text class="section-title">自填资料分析</text><text class="body-text">{{ report!.summary }}</text><text class="report-note">{{ report!.disclaimer }}</text></view>
|
||||
<view class="panel"><text class="section-title">本次提交的事实</text><text class="report-note">来源:用户自填,未经外部核验;未填写不等于零。</text><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></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 v-if="report!.suggestions.length > 0" class="panel"><text class="section-title">完善建议</text><text class="body-text" v-for="(item, index) in report!.suggestions" :key="index">{{ item }}</text></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></view>
|
||||
<view class="panel"><text class="section-title">产品匹配</text><text class="body-text">{{ report!.matchingMessage }}</text></view>
|
||||
<text class="action" @click="history">查看历史评估</text>
|
||||
<template v-else-if="tab == 'matches'">
|
||||
<text class="headline">本次需求的可选方向</text><text class="intro">以下为评估时的条件判断。展示顺序不代表优劣,也不代表机构审批。</text>
|
||||
<view v-if="report!.matching.status == 'completed'" class="stats"><view class="stat" @click="allMatches('eligible')"><text class="stat-number">{{ report!.matching.eligible }}</text><text class="stat-label">符合已知条件</text></view><view class="stat" @click="allMatches('ineligible')"><text class="stat-number">{{ report!.matching.ineligible }}</text><text class="stat-label">条件不符</text></view><view class="stat" @click="allMatches('unknown')"><text class="stat-number">{{ report!.matching.unknown }}</text><text class="stat-label">待核实</text></view></view>
|
||||
<text class="report-note">{{ report!.matchingMessage }}</text>
|
||||
<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>
|
||||
<view v-for="item in candidates" :key="item.id" class="product-card" @click="openProduct(item.id)"><view class="panel-heading"><text class="product-name">{{ item.product.name }}</text><text class="version">v{{ item.product.version }}</text></view><text class="report-note">{{ item.product.bank }} · {{ item.stateLabel }}</text><text v-if="item.product.demo" class="demo">演示产品 · 非真实金融产品</text><text class="amount">{{ item.product.amount }}</text><view class="metrics"><text class="metric">年化 {{ item.product.rate }}</text><text class="metric">{{ item.product.term }}</text></view><text class="reason" v-for="reason in item.positive.slice(0, 2)" :key="reason.id">{{ reason.text }}</text><text v-if="item.availability != 'available'" class="availability">{{ item.availabilityLabel }};仍保留历史参数。</text><text class="action">查看匹配依据与详情 ›</text></view>
|
||||
<text v-if="candidates.length > 0" class="report-note">摘要最多展示3项符合已知条件的产品,全部分组见完整匹配列表。</text>
|
||||
<button class="primary" @click="allMatches('eligible')">查看全部匹配结果</button><view class="links"><text class="action" @click="allMatches('ineligible')">查看不符原因</text><text class="action" @click="allMatches('unknown')">查看待核实项</text></view>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="headline">让下一次准备更充分</text><text class="intro">依据本次报告和产品条件整理。补充资料后需重新提交新评估,改善不保证获批。</text>
|
||||
<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 class="panel"><text class="section-title">已保存的AI改善建议</text><text v-if="report!.suggestions.length == 0" class="report-note">暂无有依据的额外建议,不代表已经完成核验。</text><text class="body-text" v-for="(item, index) in report!.suggestions" :key="index">{{ item }}</text><text class="source-note">保留报告生成时的文本与依据,不重新调用模型。</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">{{ item.stateLabel }} · 评估时版本 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>
|
||||
<text class="disclaimer">{{ report!.disclaimer }}</text>
|
||||
<view class="bottom-actions"><button class="secondary" @click="start">重新评估</button><button class="secondary" @click="history">查看历史评估</button></view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -29,40 +59,148 @@
|
||||
import AppHeader from '@/components/AppHeader.uvue'
|
||||
import { usePageLayout } from '@/services/layout.uts'
|
||||
import { getSession } from '@/services/api.uts'
|
||||
import { AnalysisReport, fetchAnalysisReport } from '@/packages/finance/services/records.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 } = usePageLayout()
|
||||
const assessmentId = ref(''); const loading = ref(false); const error = ref('')
|
||||
const report = ref<AnalysisReport | null>(null)
|
||||
const tabs: ReportTab[] = [{ id: 'overview', label: '评估概况' }, { id: 'matches', label: '匹配结果' }, { id: 'improvements', label: '改善建议' }]
|
||||
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 candidates = ref<MatchView[]>([]); const candidateLoading = ref(false); const candidateError = ref('')
|
||||
const adviceGroups = ref<AdviceGroup[]>([])
|
||||
let generation = 0; let visible = false
|
||||
const missingItems = computed((): AssessmentField[] => report.value == null ? [] : report.value!.completenessItems.filter((item: AssessmentField): boolean => ['待补充','需核实'].includes(item.value)))
|
||||
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 }
|
||||
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) uni.redirectTo({ url: '/packages/account/login/login' })
|
||||
else error.value = '账户已切换,请重新加载本人报告。'
|
||||
return true
|
||||
}
|
||||
const verifyMatches = (page: MatchPage) => {
|
||||
const value = report.value
|
||||
if (value == null || page.assessmentId != assessmentId.value || 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, 'eligible', '')
|
||||
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, '')
|
||||
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
|
||||
report.value = null; error.value = ''
|
||||
if (assessmentId.value.length == 0) { error.value = '请从融资档案选择已完成的评估记录。'; return }
|
||||
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) { uni.redirectTo({ url: '/packages/account/login/login' }); return }
|
||||
const owner = session['token'] as string
|
||||
loading.value = true
|
||||
const token = session.getString('token') ?? ''; loading.value = true
|
||||
try {
|
||||
const result = await fetchAnalysisReport(assessmentId.value)
|
||||
if (visible && request == generation && getSession()?.getString('token') == owner) report.value = result
|
||||
} catch (err) { if (visible && request == generation) error.value = err instanceof Error ? err.message : '报告加载失败,请重试' }
|
||||
if (!current(request, token)) { sessionChanged(request, token); return }
|
||||
report.value = result
|
||||
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 + '&tab=' + value }) }
|
||||
const history = () => { uni.navigateTo({ url: '/packages/finance/archive/archive' }) }
|
||||
onLoad((options) => { if (options != null) assessmentId.value = options['assessmentId'] ?? '' })
|
||||
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 + '&group=' + group }) }
|
||||
const openProduct = (id: string) => { uni.navigateTo({ url: '/packages/finance/product/product?assessmentId=' + assessmentId.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) => {
|
||||
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++; report.value = null; error.value = '' })
|
||||
onUnload(() => { visible = false; generation++ })
|
||||
// 离页丢弃个人数据;旧请求不得覆盖新页面或新登录账户。
|
||||
onHide(() => { visible = false; generation++; reset(); loading.value = false; error.value = '' })
|
||||
onUnload(() => { visible = false; generation++; reset() })
|
||||
</script>
|
||||
<style>
|
||||
.report-content { padding: 28rpx 30rpx 80rpx; }
|
||||
.tabs { height: 90rpx; flex-direction: row; background-color: #fff; border-bottom: 1rpx solid #e5ecf5; }
|
||||
.tab { flex: 1; align-items: center; justify-content: center; }
|
||||
.active { border-bottom: 4rpx solid #0868f7; }
|
||||
.tab-label { font-size: 27rpx; color: #7b8ba0; }
|
||||
.selected { color: #0868f7; font-weight: 700; }
|
||||
.report-content { padding: 28rpx 28rpx 65rpx; }
|
||||
.eyebrow { color: #778ba3; font-size: 22rpx; }
|
||||
.headline { color: #1a3859; font-size: 36rpx; font-weight: 700; margin-top: 28rpx; }
|
||||
.intro { color: #73869d; font-size: 24rpx; line-height: 40rpx; margin-top: 14rpx; }
|
||||
.hero { background-color: #155dd1; border-radius: 22rpx; padding: 32rpx; margin-top: 24rpx; }
|
||||
.hero-label { color: #d8e8ff; font-size: 24rpx; }
|
||||
.hero-title { color: #fff; font-size: 42rpx; font-weight: 700; line-height: 60rpx; margin-top: 14rpx; }
|
||||
.hero-note { color: #d8e8ff; font-size: 22rpx; line-height: 36rpx; margin-top: 12rpx; }
|
||||
.hero-divider { height: 1rpx; background-color: #4983dc; margin-top: 26rpx; margin-bottom: 24rpx; }
|
||||
.percent { color: #fff; font-size: 38rpx; font-weight: 700; }
|
||||
.progress-track { height: 12rpx; border-radius: 6rpx; background-color: #4983dc; margin-top: 18rpx; overflow: hidden; }
|
||||
.progress-fill { height: 12rpx; background-color: #bde6ff; border-radius: 6rpx; }
|
||||
.panel { margin-top: 24rpx; padding: 28rpx; border-radius: 18rpx; background-color: #fff; border: 1rpx solid #e7edf6; }
|
||||
.body-text { margin-top: 20rpx; font-size: 27rpx; color: #42536b; line-height: 44rpx; }
|
||||
.report-note { font-size: 23rpx; line-height: 38rpx; color: #78889e; margin-top: 18rpx; }
|
||||
.panel-heading { flex-direction: row; justify-content: space-between; align-items: center; }
|
||||
.section-title { color: #253f5e; font-size: 29rpx; 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; color: #738198; }
|
||||
.fact-value { flex: 1; text-align: right; font-size: 26rpx; color: #23344e; }
|
||||
.action { padding: 26rpx 0; color: #0868f7; font-size: 27rpx; }
|
||||
.readiness-value { margin-top: 20rpx; font-size: 32rpx; line-height: 48rpx; font-weight: bold; color: #23344e; }
|
||||
.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; background-color: #e9f2ff; padding: 26rpx 8rpx; border-radius: 18rpx; 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: 18rpx; 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: #8897aa; font-size: 22rpx; line-height: 36rpx; margin-top: 28rpx; }
|
||||
.bottom-actions { flex-direction: row; justify-content: space-between; }
|
||||
.bottom-actions .secondary { width: 48%; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user