Files
loan-pricing/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue

513 lines
22 KiB
Vue
Raw Normal View History

2026-01-22 09:20:28 +08:00
<template>
<el-card class="model-output-card" v-if="custType && (retailOutput || corpOutput)">
<div slot="header" class="card-header">
<span class="card-title">模型输出</span>
</div>
<div class="output-sections">
2026-01-22 09:20:28 +08:00
<!-- 个人客户模型输出 -->
<template v-if="custType === '个人' && retailOutput">
<div class="output-section">
2026-08-12 10:36:21 +08:00
<h4 class="section-title">测算结果</h4>
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="基准利率"><span class="rate-value">{{ formatRate(retailOutput.baseLoanRate) }}</span> %</el-descriptions-item>
2026-08-12 10:36:21 +08:00
<el-descriptions-item label="浮动BP"><span class="total-bp-value">{{ retailOutput.totalBp || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="模型测算利率"><span class="calculate-rate">{{ formatRate(retailOutput.calculateRate) }}</span> %</el-descriptions-item>
<el-descriptions-item label="历史利率">{{ formatRate(retailOutput.loanRateHistory) }}</el-descriptions-item>
<el-descriptions-item label="平滑幅度">{{ formatRate(retailOutput.smoothRange) }}</el-descriptions-item>
<el-descriptions-item label="平滑利率"><span class="calculate-rate">{{ formatRate(retailOutput.finalCalculateRate) }}</span> %</el-descriptions-item>
<el-descriptions-item label="BP支行权限">-</el-descriptions-item>
<el-descriptions-item label="参考利率"><span class="calculate-rate">{{ formatRate(retailOutput.referenceRate) }}</span> %</el-descriptions-item>
2026-01-22 09:20:28 +08:00
</el-descriptions>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
<h4 class="section-title">忠诚度分析</h4>
2026-08-11 15:37:32 +08:00
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
<tr v-for="(row, index) in retailLoyaltyRows" :key="`retail-loyalty-${index}`" :class="{ 'is-total-row': row.total }">
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
<h4 class="section-title">贡献度分析</h4>
2026-08-11 15:37:32 +08:00
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
2026-08-12 10:36:21 +08:00
<tr v-for="(row, index) in retailContributionRows" :key="`retail-contribution-${index}`" :class="{ 'is-total-row': row.total }">
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
<td><span v-if="row.bpLabel" :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
2026-08-11 15:37:32 +08:00
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
<h4 class="section-title">关联度分析</h4>
2026-08-11 15:37:32 +08:00
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
<tr v-for="(row, index) in retailRelevanceRows" :key="`retail-relevance-${index}`" :class="{ 'is-total-row': row.total }">
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
2026-08-11 15:37:32 +08:00
<h4 class="section-title">风险度分析</h4>
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
<tr v-for="(row, index) in retailRiskRows" :key="`retail-risk-${index}`" :class="{ 'is-total-row': row.total }">
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
</template>
<!-- 企业客户模型输出 -->
<template v-else-if="custType === '企业' && corpOutput">
<div class="output-section">
2026-08-12 10:36:21 +08:00
<h4 class="section-title">测算结果</h4>
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="基准利率"><span class="rate-value">{{ formatRate(corpOutput.baseLoanRate) }}</span> %</el-descriptions-item>
2026-08-12 10:36:21 +08:00
<el-descriptions-item label="浮动BP"><span class="total-bp-value">{{ corpOutput.totalBp || '-' }}</span></el-descriptions-item>
<el-descriptions-item label="模型测算利率"><span class="calculate-rate">{{ formatRate(corpOutput.calculateRate) }}</span> %</el-descriptions-item>
<el-descriptions-item label="历史利率">{{ formatRate(corpOutput.loanRateHistory) }}</el-descriptions-item>
<el-descriptions-item label="平滑幅度">{{ formatRate(corpOutput.smoothRange) }}</el-descriptions-item>
<el-descriptions-item label="平滑利率"><span class="calculate-rate">{{ formatRate(corpOutput.finalCalculateRate) }}</span> %</el-descriptions-item>
<el-descriptions-item label="BP支行权限">-</el-descriptions-item>
<el-descriptions-item label="参考利率"><span class="calculate-rate">{{ formatRate(corpOutput.referenceRate) }}</span> %</el-descriptions-item>
2026-01-22 09:20:28 +08:00
</el-descriptions>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
<h4 class="section-title">忠诚度分析</h4>
2026-08-11 15:37:32 +08:00
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
<tr v-for="(row, index) in corporateLoyaltyRows" :key="`corporate-loyalty-${index}`" :class="{ 'is-total-row': row.total }">
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
<h4 class="section-title">贡献度分析</h4>
2026-08-11 15:37:32 +08:00
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
2026-08-12 10:36:21 +08:00
<tr v-for="(row, index) in corporateContributionRows" :key="`corporate-contribution-${index}`" :class="{ 'is-total-row': row.total }">
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
<td><span v-if="row.bpLabel" :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
2026-08-11 15:37:32 +08:00
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
<h4 class="section-title">关联度分析</h4>
2026-08-11 15:37:32 +08:00
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
<tr v-for="(row, index) in corporateRelevanceRows" :key="`corporate-relevance-${index}`" :class="{ 'is-total-row': row.total }">
2026-08-12 10:36:21 +08:00
<td><span v-if="row.summary">汇总</span><template v-else><div v-if="row.category" class="result-category">{{ row.category }}</div><div v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></template></td>
2026-08-11 15:37:32 +08:00
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
<div class="output-section">
2026-08-11 15:37:32 +08:00
<h4 class="section-title">风险度分析</h4>
<table class="output-analysis-table">
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
<tbody>
<tr v-for="(row, index) in corporateRiskRows" :key="`corporate-risk-${index}`" :class="{ 'is-total-row': row.total }">
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
</tr>
</tbody>
</table>
</div>
2026-01-22 09:20:28 +08:00
</template>
</div>
2026-01-22 09:20:28 +08:00
</el-card>
</template>
<script>
import {formatRate} from "@/utils/rate"
2026-01-22 09:20:28 +08:00
export default {
name: "ModelOutputDisplay",
props: {
custType: {
type: String,
default: null
},
retailOutput: {
type: Object,
default: null
},
corpOutput: {
type: Object,
default: null
}
},
2026-08-11 15:37:32 +08:00
computed: {
retailLoyaltyRows() {
return [
this.analysisRow('我行首贷客户', this.formatBoolean(this.retailOutput.isFirstLoan), 'BP_首贷', this.retailOutput.bpFirstLoan),
this.analysisRow('用信天数', this.retailOutput.faithDay, 'BP_贷龄', this.retailOutput.bpAgeLoan),
this.analysisRow('客户年龄', this.retailOutput.custAge, 'BP_年龄', this.retailOutput.bpAge),
this.totalRow('TOTAL_BP_忠诚度', this.retailOutput.totalBpLoyalty)
]
},
retailContributionRows() {
2026-08-12 10:36:21 +08:00
return [
this.groupedAnalysisRow([
this.resultItem('存款年日均', this.retailOutput.balanceAvg),
this.resultItem('贷款年日均', this.retailOutput.loanAvg),
this.resultItem('派生率', this.formatRate(this.retailOutput.derivationRate))
]),
this.totalRow('TOTAL_BP_贡献度', this.retailOutput.totalBpContribution)
]
2026-08-11 15:37:32 +08:00
},
retailRelevanceRows() {
return [
this.groupedAnalysisRow([
this.resultItem('中间业务_个人_信用卡', this.formatBoolean(this.retailOutput.midPerCard)),
this.resultItem('中间业务_个人_一码通', this.formatBoolean(this.retailOutput.midPerPass)),
this.resultItem('中间业务_个人_丰收互联', this.formatBoolean(this.retailOutput.midPerHarvest)),
this.resultItem('中间业务_个人_有效客户', this.formatBoolean(this.retailOutput.midPerEffect)),
this.resultItem('中间业务_个人_快捷支付', this.formatBoolean(this.retailOutput.midPerQuickPay)),
this.resultItem('中间业务_个人_电费代扣', this.formatBoolean(this.retailOutput.midPerEleDdc)),
this.resultItem('中间业务_个人_水费代扣', this.formatBoolean(this.retailOutput.midPerWaterDdc)),
this.resultItem('中间业务_个人_华数费代扣', this.formatBoolean(this.retailOutput.midPerHuashuDdc)),
this.resultItem('中间业务_个人_煤气费代扣', this.formatBoolean(this.retailOutput.MidPerGasDdc)),
this.resultItem('中间业务_个人_市民卡', this.formatBoolean(this.retailOutput.midPerCitizencard)),
this.resultItem('中间业务_个人_理财业务', this.formatBoolean(this.retailOutput.midPerFinMan)),
this.resultItem('中间业务_个人_etc', this.formatBoolean(this.retailOutput.midPerEtc))
], 'BP_中间业务', this.retailOutput.bpMid),
this.totalRow('TOTAL_BP_关联度', this.retailOutput.totalBpRelevance)
]
},
retailRiskRows() {
return [
this.analysisRow('申请金额', this.retailOutput.applyAmt, 'BP_贷款额度', this.retailOutput.bpLoanAmount),
this.groupedAnalysisRow([
this.resultItem('贷款用途', this.formatLoanPurpose(this.retailOutput.loanPurpose)),
this.resultItem('是否有经营佐证', this.formatBoolean(this.retailOutput.bizProof))
], 'BP_贷款用途', this.retailOutput.bpLoanUse),
this.analysisRow('循环功能', this.formatBoolean(this.retailOutput.loanLoop), 'BP_循环功能', this.retailOutput.bpLoanLoop),
this.groupedAnalysisRow([
this.resultItem('抵质押类型', this.retailOutput.collType),
this.resultItem('抵质押物三方所有', this.formatBoolean(this.retailOutput.collThirdParty))
], 'BP_抵押物', this.retailOutput.bpCollateral),
this.groupedAnalysisRow([
this.resultItem('灰名单客户', this.formatBoolean(this.retailOutput.greyCust)),
this.resultItem('本金逾期', this.formatBoolean(this.retailOutput.prinOverdue)),
this.resultItem('利息逾期', this.formatBoolean(this.retailOutput.interestOverdue)),
this.resultItem('信用卡逾期', this.formatBoolean(this.retailOutput.cardOverdue))
], 'BP_灰名单与逾期', this.retailOutput.bpGreyOverdue),
this.totalRow('TOTAL_BP_风险度', this.retailOutput.totalBpRisk)
]
},
corporateLoyaltyRows() {
return [
this.analysisRow('我行首贷客户', this.formatBoolean(this.corpOutput.isFirstLoan), 'BP_首贷', this.corpOutput.bpFirstLoan),
this.analysisRow('用信天数', this.corpOutput.faithDay, 'BP_贷龄', this.corpOutput.bpAgeLoan),
this.totalRow('TOTAL_BP_忠诚度', this.corpOutput.totalBpLoyalty)
]
},
corporateContributionRows() {
2026-08-12 10:36:21 +08:00
return [
this.groupedAnalysisRow([
this.resultItem('存款年日均', this.corpOutput.balanceAvg),
this.resultItem('贷款年日均', this.corpOutput.loanAvg),
this.resultItem('派生率', this.formatRate(this.corpOutput.derivationRate))
]),
this.totalRow('TOTAL_BP_贡献度', this.corpOutput.totalBpContribution)
]
2026-08-11 15:37:32 +08:00
},
corporateRelevanceRows() {
return [
this.groupedAnalysisRow([
this.resultItem('中间业务_企业_企业互联', this.formatBoolean(this.corpOutput.midEntConnect)),
this.resultItem('中间业务_企业_有效价值客户', this.formatBoolean(this.corpOutput.midEntEffect)),
this.resultItem('中间业务_企业_国际业务', this.formatBoolean(this.corpOutput.midEntInter)),
this.resultItem('中间业务_企业_承兑', this.formatBoolean(this.corpOutput.midEntAccept)),
this.resultItem('中间业务_企业_贴现', this.formatBoolean(this.corpOutput.midEntDiscount)),
this.resultItem('中间业务_企业_电费代扣', this.formatBoolean(this.corpOutput.midEntEleDdc)),
this.resultItem('中间业务_企业_水费代扣', this.formatBoolean(this.corpOutput.midEntWaterDdc))
2026-08-12 10:36:21 +08:00
], 'BP_中间业务', this.corpOutput.bpMid, false, '中间业务'),
2026-08-11 15:37:32 +08:00
this.groupedAnalysisRow([
this.resultItem('代发工资户数', this.corpOutput.payroll),
this.resultItem('存量贷款余额', this.corpOutput.invLoanAmount)
2026-08-12 10:36:21 +08:00
], 'BP_代发工资', this.corpOutput.bpPayroll, false, '代发工资'),
this.groupedAnalysisRow([
this.resultItem('净身企业', this.formatBoolean(this.corpOutput.isCleanEnt)),
this.resultItem('开立基本结算账户', this.formatBoolean(this.corpOutput.hasSettleAcct)),
this.resultItem('绿色贷款', this.formatBoolean(this.corpOutput.isGreenLoan)),
this.resultItem('科技型企业', this.formatBoolean(this.corpOutput.isTechEnt)),
this.resultItem('贸易和建筑业企业', this.formatBoolean(this.corpOutput.isTradeBuildEnt))
], 'BP_企业客户类别', this.corpOutput.bpEntType, false, '客户类型'),
2026-08-11 15:37:32 +08:00
this.totalRow('TOTAL_BP_关联度', this.corpOutput.totalBpRelevance)
]
},
corporateRiskRows() {
return [
this.analysisRow('借款期限', this.corpOutput.loanTerm, 'BP_贷款期限', this.corpOutput.bpLoanTerm),
this.analysisRow('申请金额', this.corpOutput.applyAmt, 'BP_贷款额度', this.corpOutput.bpLoanAmount),
this.groupedAnalysisRow([
this.resultItem('抵质押类型', this.corpOutput.collType),
this.resultItem('抵质押物三方所有', this.formatBoolean(this.corpOutput.collThirdParty))
], 'BP_抵押物', this.corpOutput.bpCollateral),
this.groupedAnalysisRow([
this.resultItem('灰名单客户', this.formatBoolean(this.corpOutput.greyCust)),
this.resultItem('本金逾期', this.formatBoolean(this.corpOutput.prinOverdue)),
this.resultItem('利息逾期', this.formatBoolean(this.corpOutput.interestOverdue))
], 'BP_灰名单与逾期', this.corpOutput.bpGreyOverdue),
this.totalRow('TOTAL_BP_风险度', this.corpOutput.totalBpRisk)
]
}
},
2026-01-22 09:20:28 +08:00
methods: {
formatRate,
2026-08-11 15:37:32 +08:00
resultItem(label, value) {
return {label, value: this.formatOutputValue(value)}
},
2026-08-12 10:36:21 +08:00
groupedAnalysisRow(results, bpLabel, bpValue, total = false, category = null) {
return {results, bpLabel, bpValue: this.formatOutputValue(bpValue), total, category}
2026-08-11 15:37:32 +08:00
},
analysisRow(resultLabel, resultValue, bpLabel, bpValue) {
return this.groupedAnalysisRow([this.resultItem(resultLabel, resultValue)], bpLabel, bpValue)
},
totalRow(bpLabel, bpValue) {
return {summary: true, bpLabel, bpValue: this.formatOutputValue(bpValue), total: true}
},
2026-01-22 09:20:28 +08:00
/** 格式化布尔值为中文 */
formatBoolean(value) {
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
if (value === 'false' || value === false || value === '0' || value === 0) return '否'
2026-01-22 09:20:28 +08:00
return value || '-'
},
/** 格式化贷款用途 */
formatLoanPurpose(value) {
if (value === 'consumer') return '消费贷款'
if (value === 'business') return '经营贷款'
return value || '-'
},
formatOutputValue(value) {
if (value === 0) return '0'
return value || '-'
2026-01-22 09:20:28 +08:00
}
}
}
</script>
<style lang="scss" scoped>
.model-output-card {
::v-deep .el-card__header {
padding: 16px 20px;
background-color: #fafafa;
border-bottom: 1px solid #ebeef5;
}
.card-header {
display: flex;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
}
::v-deep .el-card__body {
padding: 20px;
}
.output-section + .output-section {
margin-top: 24px;
}
.section-title {
margin: 0 0 12px;
font-size: 15px;
font-weight: 500;
color: #303133;
2026-01-22 09:20:28 +08:00
}
2026-08-11 15:37:32 +08:00
.output-analysis-table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
border: 1px solid #ebeef5;
color: #606266;
font-size: 13px;
.result-column {
width: 60%;
}
.bp-column {
width: 40%;
}
th,
td {
padding: 10px 12px;
border-right: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
text-align: left;
vertical-align: top;
overflow-wrap: anywhere;
word-break: break-word;
}
th:last-child,
td:last-child {
border-right: 0;
}
tbody tr:last-child td {
border-bottom: 0;
}
th {
background: #f5f7fa;
color: #909399;
font-weight: 500;
}
.result-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
flex-wrap: wrap;
gap: 4px 12px;
}
.result-item + .result-item {
margin-top: 6px;
padding-top: 6px;
border-top: 1px dashed #ebeef5;
}
2026-08-12 10:36:21 +08:00
.result-category {
margin-bottom: 8px;
color: #303133;
font-weight: 600;
}
2026-08-11 15:37:32 +08:00
.result-label {
min-width: 0;
color: #606266;
}
.result-value {
margin-left: auto;
color: #303133;
font-weight: 500;
text-align: right;
}
.is-total-row {
background: #fffaf0;
td:first-child {
color: #606266;
font-weight: 500;
}
}
}
2026-01-22 09:20:28 +08:00
// BP 值样式
.bp-value {
color: #409eff;
font-weight: 500;
}
// TOTAL_BP 样式
.total-bp-value {
color: #e6a23c;
font-weight: 600;
}
// 测算利率高亮样式
.calculate-rate {
color: #f56c6c;
font-weight: 600;
font-size: 16px;
}
// 利率值样式
.rate-value {
color: #67c23a;
font-weight: 500;
}
}
2026-08-11 15:37:32 +08:00
@media screen and (max-width: 768px) {
.model-output-card {
::v-deep .el-card__body {
padding: 16px;
}
.output-analysis-table {
th,
td {
padding: 8px;
}
}
}
}
@media print {
.model-output-card {
break-inside: auto !important;
page-break-inside: auto !important;
.output-section {
break-inside: auto;
page-break-inside: auto;
}
.output-analysis-table {
color: #000;
thead {
display: table-header-group;
}
tr {
break-inside: avoid !important;
page-break-inside: avoid !important;
}
}
}
}
2026-01-22 09:20:28 +08:00
</style>