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

513 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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">
<!-- 个人客户模型输出 -->
<template v-if="custType === '个人' && retailOutput">
<div class="output-section">
<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>
<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>
</el-descriptions>
</div>
<div class="output-section">
<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 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>
<div class="output-section">
<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 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>
</tr>
</tbody>
</table>
</div>
<div class="output-section">
<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 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>
<div class="output-section">
<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>
</template>
<!-- 企业客户模型输出 -->
<template v-else-if="custType === '企业' && corpOutput">
<div class="output-section">
<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>
<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>
</el-descriptions>
</div>
<div class="output-section">
<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 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>
<div class="output-section">
<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 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>
</tr>
</tbody>
</table>
</div>
<div class="output-section">
<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 corporateRelevanceRows" :key="`corporate-relevance-${index}`" :class="{ 'is-total-row': row.total }">
<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>
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}{{ row.bpValue }}</span></td>
</tr>
</tbody>
</table>
</div>
<div class="output-section">
<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>
</template>
</div>
</el-card>
</template>
<script>
import {formatRate} from "@/utils/rate"
export default {
name: "ModelOutputDisplay",
props: {
custType: {
type: String,
default: null
},
retailOutput: {
type: Object,
default: null
},
corpOutput: {
type: Object,
default: null
}
},
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() {
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)
]
},
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() {
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)
]
},
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))
], 'BP_中间业务', this.corpOutput.bpMid, false, '中间业务'),
this.groupedAnalysisRow([
this.resultItem('代发工资户数', this.corpOutput.payroll),
this.resultItem('存量贷款余额', this.corpOutput.invLoanAmount)
], '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, '客户类型'),
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)
]
}
},
methods: {
formatRate,
resultItem(label, value) {
return {label, value: this.formatOutputValue(value)}
},
groupedAnalysisRow(results, bpLabel, bpValue, total = false, category = null) {
return {results, bpLabel, bpValue: this.formatOutputValue(bpValue), total, category}
},
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}
},
/** 格式化布尔值为中文 */
formatBoolean(value) {
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
if (value === 'false' || value === false || value === '0' || value === 0) return '否'
return value || '-'
},
/** 格式化贷款用途 */
formatLoanPurpose(value) {
if (value === 'consumer') return '消费贷款'
if (value === 'business') return '经营贷款'
return value || '-'
},
formatOutputValue(value) {
if (value === 0) return '0'
return value || '-'
}
}
}
</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;
}
.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;
}
.result-category {
margin-bottom: 8px;
color: #303133;
font-weight: 600;
}
.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;
}
}
}
// 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;
}
}
@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;
}
}
}
}
</style>