1
This commit is contained in:
246
src/ui/CJRZQ5E9F/README.md
Normal file
246
src/ui/CJRZQ5E9F/README.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# 贷款风险报告组件 (CJRZQ5E9F) - 模块化架构
|
||||
|
||||
## 概述
|
||||
|
||||
贷款风险报告组件采用模块化架构设计,将完整的贷款风险评估拆分成7个独立的模块,每个模块都可以作为独立的tab显示,具有独立的大标题。
|
||||
|
||||
## 数据结构
|
||||
|
||||
贷款风险报告的数据结构如下:
|
||||
|
||||
```javascript
|
||||
{
|
||||
"apiID": "CJRZQ5E9F",
|
||||
"data": {
|
||||
"xyp_cpl0001": "5", // 贷款总机构数
|
||||
"xyp_cpl0002": "3", // 已结清机构数
|
||||
"xyp_cpl0007": "2", // 消费金融类机构数
|
||||
"xyp_cpl0008": "1", // 网络贷款类机构数
|
||||
"xyp_cpl0009": "1", // 最近7天机构数
|
||||
"xyp_cpl0014": "15", // 历史成功还款笔数
|
||||
"xyp_cpl0015": "2", // 历史失败还款笔数
|
||||
"xyp_cpl0016": "1", // 最近1天失败笔数
|
||||
"xyp_cpl0017": "3", // 最近1天成功笔数
|
||||
"xyp_cpl0018": "1", // 最近7天失败笔数
|
||||
"xyp_cpl0019": "5", // 最近7天成功笔数
|
||||
"xyp_cpl0020": "1", // 最近14天失败笔数
|
||||
"xyp_cpl0021": "7", // 最近14天成功笔数
|
||||
"xyp_cpl0022": "2", // 最近30天失败笔数
|
||||
"xyp_cpl0023": "10", // 最近30天成功笔数
|
||||
"xyp_cpl0024": "2", // 最近90天失败笔数
|
||||
"xyp_cpl0025": "12", // 最近90天成功笔数
|
||||
"xyp_cpl0026": "2", // 最近180天失败笔数
|
||||
"xyp_cpl0027": "14", // 最近180天成功笔数
|
||||
"xyp_cpl0028": "0", // 最近1天逾期标识
|
||||
"xyp_cpl0029": "0", // 最近7天逾期标识
|
||||
"xyp_cpl0030": "0", // 最近14天逾期标识
|
||||
"xyp_cpl0031": "0", // 最近30天逾期标识
|
||||
"xyp_cpl0032": "1000", // 最近1天失败金额
|
||||
"xyp_cpl0033": "5000", // 最近1天成功金额
|
||||
"xyp_cpl0034": "2000", // 最近7天失败金额
|
||||
"xyp_cpl0035": "8000", // 最近7天成功金额
|
||||
"xyp_cpl0036": "1500", // 最近14天失败金额
|
||||
"xyp_cpl0037": "12000", // 最近14天成功金额
|
||||
"xyp_cpl0038": "3000", // 最近30天失败金额
|
||||
"xyp_cpl0039": "20000", // 最近30天成功金额
|
||||
"xyp_cpl0040": "4000", // 最近90天失败金额
|
||||
"xyp_cpl0041": "35000", // 最近90天成功金额
|
||||
"xyp_cpl0042": "5000", // 最近180天失败金额
|
||||
"xyp_cpl0043": "45000", // 最近180天成功金额
|
||||
"xyp_cpl0044": "0", // 当前逾期状态
|
||||
"xyp_cpl0045": "365", // 信用贷款时长
|
||||
"xyp_cpl0046": "30", // 最近一次交易距今天数
|
||||
"xyp_cpl0064": "1", // 最近21天成功笔数
|
||||
"xyp_cpl0065": "0", // 最近21天失败笔数
|
||||
"xyp_cpl0066": "1000", // 最近21天失败金额
|
||||
"xyp_cpl0067": "6000", // 最近21天成功金额
|
||||
"xyp_cpl0068": "15", // 最近一次还款距今天数
|
||||
"xyp_cpl0070": "1", // 最近1天机构数
|
||||
"xyp_cpl0071": "0", // 当前逾期机构数
|
||||
"xyp_cpl0072": "0", // 当前逾期金额
|
||||
"xyp_cpl0073": "0.85", // 近5次金额成功率
|
||||
"xyp_cpl0074": "0.80", // 近5次还款成功率
|
||||
"xyp_cpl0075": "0.75", // 近20次小贷成功率
|
||||
"xyp_cpl0079": "0.70", // 近90天金额成功率
|
||||
"xyp_cpl0080": "0.65", // 近90天还款成功率
|
||||
"xyp_cpl0081": "0.25", // 信用风险评分
|
||||
"xyp_cpl0082": "0.30", // 履约金额综合指数
|
||||
"xyp_cpl0083": "0.35", // 履约笔数综合指数
|
||||
"xyp_model_score_high": "750", // 小额网贷分
|
||||
"xyp_model_score_mid": "680", // 小额分期分
|
||||
"xyp_model_score_low": "720", // 中大额分期分
|
||||
"xyp_t0400002": "0.78", // 近20次还款成功率
|
||||
"xyp_t0400003": "0.82", // 近50次还款成功率
|
||||
"xyp_t0400004": "0.80" // 近100次还款成功率
|
||||
},
|
||||
"success": true,
|
||||
"timestamp": "2025-01-20 21:19:58"
|
||||
}
|
||||
```
|
||||
|
||||
## 模块拆分
|
||||
|
||||
贷款风险报告被拆分成以下7个独立模块:
|
||||
|
||||
| API ID | 模块名称 | 包含数据 | 组件文件 |
|
||||
|--------|----------|----------|----------|
|
||||
| `CJRZQ5E9F_RiskOverview` | 风险概览 | 综合风险等级、当前状态、关键指标 | RiskOverview.vue |
|
||||
| `CJRZQ5E9F_CreditScores` | 信用评分 | 综合信用指数、专业模型评分、还款表现 | CreditScores.vue |
|
||||
| `CJRZQ5E9F_LoanBehaviorAnalysis` | 贷款行为分析 | 机构类型分布、还款表现统计、时间维度分析 | LoanBehaviorAnalysis.vue |
|
||||
| `CJRZQ5E9F_InstitutionAnalysis` | 机构分析 | 机构类型分析、合作机构详情 | InstitutionAnalysis.vue |
|
||||
| `CJRZQ5E9F_TimeTrendAnalysis` | 时间趋势分析 | 历史趋势、周期性分析 | TimeTrendAnalysis.vue |
|
||||
| `CJRZQ5E9F_RiskIndicators` | 风险指标详情 | 详细风险指标、风险因子分析 | RiskIndicators.vue |
|
||||
| `CJRZQ5E9F_RiskAdvice` | 专业建议 | 风险评估建议、优化建议 | RiskAdvice.vue |
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 前端自动拆分
|
||||
|
||||
BaseReport.vue 已自动配置支持贷款风险报告的模块化显示:
|
||||
|
||||
```javascript
|
||||
import { splitCJRZQ5E9FForTabs } from '@/ui/CJRZQ5E9F/utils/simpleSplitter.js';
|
||||
|
||||
// 处理数据拆分(支持DWBG8B4D、DWBG6A2C和CJRZQ5E9F)
|
||||
const processedReportData = computed(() => {
|
||||
let data = reportData.value;
|
||||
|
||||
// 拆分DWBG8B4D数据
|
||||
data = splitDWBG8B4DForTabs(data);
|
||||
|
||||
// 拆分DWBG6A2C数据
|
||||
data = splitDWBG6A2CForTabs(data);
|
||||
|
||||
// 拆分CJRZQ5E9F数据
|
||||
data = splitCJRZQ5E9FForTabs(data);
|
||||
|
||||
return data;
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 组件配置
|
||||
|
||||
BaseReport.vue 中已配置所有贷款风险报告模块:
|
||||
|
||||
```javascript
|
||||
// 贷款风险报告
|
||||
JRZQ5E9F: {
|
||||
name: "贷款风险评估",
|
||||
component: defineAsyncComponent(() => import("@/ui/CJRZQ5E9F/index.vue")),
|
||||
remark: '贷款风险评估提供全面的个人贷款风险分析,包括风险概览、信用评分、贷款行为分析、机构分析等多维度评估。'
|
||||
},
|
||||
// ... 其他模块配置
|
||||
```
|
||||
|
||||
## 组件结构
|
||||
|
||||
```
|
||||
src/ui/CJRZQ5E9F/
|
||||
├── index.vue # 原始完整组件(保留)
|
||||
├── README.md # 文档说明
|
||||
├── components/ # 子组件目录
|
||||
│ ├── RiskOverview.vue # 风险概览
|
||||
│ ├── CreditScores.vue # 信用评分
|
||||
│ ├── LoanBehaviorAnalysis.vue # 贷款行为分析
|
||||
│ ├── InstitutionAnalysis.vue # 机构分析
|
||||
│ ├── TimeTrendAnalysis.vue # 时间趋势分析
|
||||
│ ├── RiskIndicators.vue # 风险指标详情
|
||||
│ └── RiskAdvice.vue # 专业建议
|
||||
└── utils/
|
||||
└── simpleSplitter.js # 数据拆分工具
|
||||
```
|
||||
|
||||
## 特色功能
|
||||
|
||||
### 1. 智能风险评估
|
||||
- 多维度风险等级计算
|
||||
- 智能颜色编码
|
||||
- 动态风险提示
|
||||
|
||||
### 2. 数据可视化
|
||||
- 渐变色彩设计
|
||||
- 图标化展示
|
||||
- 响应式布局
|
||||
- 交互式图表
|
||||
|
||||
### 3. 用户友好
|
||||
- 清晰的层次结构
|
||||
- 详细的说明文档
|
||||
- 直观的风险提示
|
||||
- 专业的建议指导
|
||||
|
||||
### 4. 模块化设计
|
||||
- 独立的模块组件
|
||||
- 可复用的工具函数
|
||||
- 灵活的数据拆分
|
||||
- 易于维护和扩展
|
||||
|
||||
## 工具函数
|
||||
|
||||
`utils/simpleSplitter.js` 提供了以下工具函数:
|
||||
|
||||
- `splitCJRZQ5E9FForTabs()` - 数据拆分
|
||||
- `parseIntervalValue()` - 解析区间化数值
|
||||
- `formatMetricValue()` - 格式化指标值
|
||||
- `formatDays()` - 格式化天数显示
|
||||
- `formatAmount()` - 格式化金额显示
|
||||
- `calculateRiskLevel()` - 计算风险等级
|
||||
- `calculateCreditScore()` - 计算信用评分
|
||||
- `getCreditScoreLevel()` - 获取信用等级描述
|
||||
- `getCreditScoreBadgeClass()` - 获取信用等级样式
|
||||
- `getScoreClass()` - 获取评分样式
|
||||
- `getCircleStyle()` - 获取圆形进度样式
|
||||
- `hasRiskData()` - 检查是否有风险数据
|
||||
|
||||
## 使用示例
|
||||
|
||||
```javascript
|
||||
// 在页面中使用
|
||||
<BaseReport
|
||||
:reportData="reportData"
|
||||
:reportParams="reportParams"
|
||||
reportName="贷款风险评估"
|
||||
feature="CJRZQ5E9F"
|
||||
:isEmpty="false"
|
||||
:isDone="true"
|
||||
/>
|
||||
```
|
||||
|
||||
## 数据字段说明
|
||||
|
||||
### 主要指标字段
|
||||
- `xyp_cpl0001`: 贷款总机构数
|
||||
- `xyp_cpl0002`: 已结清机构数
|
||||
- `xyp_cpl0044`: 当前逾期状态 (0: 无逾期, 1: 有逾期)
|
||||
- `xyp_cpl0081`: 信用风险评分 (0-1)
|
||||
- `xyp_cpl0082`: 履约金额综合指数 (0-1)
|
||||
- `xyp_cpl0083`: 履约笔数综合指数 (0-1)
|
||||
|
||||
### 模型评分字段
|
||||
- `xyp_model_score_high`: 小额网贷分 (350-950)
|
||||
- `xyp_model_score_mid`: 小额分期分 (350-950)
|
||||
- `xyp_model_score_low`: 中大额分期分 (350-950)
|
||||
|
||||
### 还款表现字段
|
||||
- `xyp_cpl0073`: 近5次金额成功率
|
||||
- `xyp_cpl0074`: 近5次还款成功率
|
||||
- `xyp_t0400002`: 近20次还款成功率
|
||||
- `xyp_t0400003`: 近50次还款成功率
|
||||
- `xyp_t0400004`: 近100次还款成功率
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保数据格式符合贷款风险报告的标准结构
|
||||
2. 所有模块都支持数据为空的情况
|
||||
3. 风险评估基于实际数据动态计算
|
||||
4. 组件采用 Tailwind CSS 进行样式设计
|
||||
5. 支持移动端响应式布局
|
||||
6. 区间化数值会自动解析为具体数值进行显示
|
||||
|
||||
## 更新日志
|
||||
|
||||
- v1.0.0: 初始版本,支持完整的贷款风险报告模块化显示
|
||||
- 包含7个独立模块
|
||||
- 支持自动数据拆分
|
||||
- 提供完整的风险评估功能
|
||||
- 支持多种数据可视化方式
|
||||
579
src/ui/CJRZQ5E9F/components/CreditScores.vue
Normal file
579
src/ui/CJRZQ5E9F/components/CreditScores.vue
Normal file
@@ -0,0 +1,579 @@
|
||||
<template>
|
||||
<div class="rounded-lg border border-[#99999933]">
|
||||
<div class="mb-4">
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center mb-4 p-4">
|
||||
<div class="w-8 h-8 flex items-center justify-center mr-2">
|
||||
<img src="@/assets/images/report/xypf2.png" alt="信用评分" class="w-8 h-8 object-contain" />
|
||||
</div>
|
||||
<span class="font-bold text-gray-800">信用评分</span>
|
||||
</div>
|
||||
|
||||
<div class="pb-4">
|
||||
<!-- 综合信用指数 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="综合信用指数" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">基于多维度风险评估的综合评分</p>
|
||||
|
||||
<!-- 信用分仪表盘 -->
|
||||
<div class="flex flex-col items-center mb-4">
|
||||
<div ref="chartRef" :style="{ width: '100%', height: '200px' }"></div>
|
||||
<div class="text-center mt-[-14px]">
|
||||
<div class=" text-[#999999]">评分范围: 150-1000</div>
|
||||
<div class="px-10 py-1 rounded-full font-medium inline-block mt-2" :class="getCreditScoreBadgeClass()">
|
||||
{{ getCreditScoreLevel() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 详细指标 -->
|
||||
<div class="space-y-3 px-4 ">
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<div class="flex items-start">
|
||||
<img src="@/assets/images/report/zq.png" alt="信用风险评分"
|
||||
class="w-10 h-10 object-contain mr-4 flex-shrink-0" />
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-800">信用风险评分</span>
|
||||
<span class="text-sm font-bold" :class="getScoreTextClass()">{{ (creditRiskScore * 100).toFixed(0)
|
||||
}}%</span>
|
||||
</div>
|
||||
<div class="h-2" :style="`background-color: ${getLightScoreColor()}`">
|
||||
<div class="h-2 transition-all duration-500"
|
||||
:style="`width: ${Math.max(creditRiskScore * 100, 2)}%; background-color: ${getScoreColor()}`">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<div class="flex items-start">
|
||||
<img src="@/assets/images/report/zq.png" alt="履约金额综合指数"
|
||||
class="w-10 h-10 object-contain mr-4 flex-shrink-0" />
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-800">履约金额综合指数</span>
|
||||
<span class="text-sm font-bold" :class="getScoreTextClass()">{{ (amountComplianceIndex *
|
||||
100).toFixed(0)
|
||||
}}%</span>
|
||||
</div>
|
||||
<div class="h-2" :style="`background-color: ${getLightScoreColor()}`">
|
||||
<div class="h-2 transition-all duration-500"
|
||||
:style="`width: ${Math.max(amountComplianceIndex * 100, 2)}%; background-color: ${getScoreColor()}`">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-green-50 rounded-lg p-4 border border-green-200">
|
||||
<div class="flex items-start">
|
||||
<img src="@/assets/images/report/zq.png" alt="履约笔数综合指数"
|
||||
class="w-10 h-10 object-contain mr-4 flex-shrink-0" />
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-sm font-medium text-gray-800">履约笔数综合指数</span>
|
||||
<span class="text-sm font-bold" :class="getScoreTextClass()">{{ (countComplianceIndex *
|
||||
100).toFixed(0)
|
||||
}}%</span>
|
||||
</div>
|
||||
<div class="h-2" :style="`background-color: ${getLightScoreColor()}`">
|
||||
<div class="h-2 transition-all duration-500"
|
||||
:style="`width: ${Math.max(countComplianceIndex * 100, 2)}%; background-color: ${getScoreColor()}`">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 专业模型评分 -->
|
||||
<div class=" mb-4">
|
||||
<div class="">
|
||||
<LTitle title="专业模型评分" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">星耀Pro系列AI评分模型</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 小额网贷分 V1 -->
|
||||
<div :class="getModelCardClass(highRiskScore)">
|
||||
<div class="flex items-center mb-2">
|
||||
<img :src="getModelIcon(highRiskScore)" alt="小额网贷分 V1"
|
||||
class="w-10 h-10 object-contain mr-3 flex-shrink-0" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-gray-800 mb-1">小额网贷分 V1</div>
|
||||
<div class="text-sm text-gray-600">针对小额网贷风险评估</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 ml-[52px]">评分: <span class="font-bold">350-950</span></div>
|
||||
<div class="absolute top-0 right-0 bg-[#999999] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatModelScore(highRiskScore) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 小额分期分 V1 -->
|
||||
<div :class="getModelCardClass(midRiskScore)">
|
||||
<div class="flex items-center mb-2">
|
||||
<img :src="getModelIcon(midRiskScore)" alt="小额分期分 V1"
|
||||
class="w-10 h-10 object-contain mr-3 flex-shrink-0" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-gray-800 mb-1">小额分期分 V1</div>
|
||||
<div class="text-sm text-gray-600">针对小额分期产品评估</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 ml-[52px]">评分: <span class="font-bold">350-950</span></div>
|
||||
<div class="absolute top-0 right-0 bg-[#999999] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatModelScore(midRiskScore) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中大额分期分 V1 -->
|
||||
<div :class="getModelCardClass(lowRiskScore)">
|
||||
<div class="flex items-center mb-2">
|
||||
<img :src="getModelIcon(lowRiskScore)" alt="中大额分期分 V1"
|
||||
class="w-10 h-10 object-contain mr-3 flex-shrink-0" />
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-gray-800 mb-1">中大额分期分 V1</div>
|
||||
<div class="text-sm text-gray-600">针对中大额分期产品评估</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 ml-[52px]">评分: <span class="font-bold">350-950</span></div>
|
||||
<div class="absolute top-0 right-0 bg-[#999999] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatModelScore(lowRiskScore) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 还款表现指标 -->
|
||||
<div class="mb-8">
|
||||
<div class="">
|
||||
<LTitle title="还款表现指标" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">近期还款成功率统计</p>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-3 px-4">
|
||||
<!-- 近5次 -->
|
||||
<div class="bg-[#ECF9EF] rounded-lg p-3 border border-[#91D69F]">
|
||||
<div class="flex flex-col items-center justify-center h-full w-full">
|
||||
<div class="flex flex-col items-center justify-center w-full h-full text-center">
|
||||
<div class="text-lg text-[#999999] mt-1">金额: <span class="font-bold text-green-600 text-2xl">{{
|
||||
(recent5AmountRatio * 100).toFixed(0) }}</span> %</div>
|
||||
<div class="font-medium text-[#666666] mb-2">近5次</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近20次 -->
|
||||
<div class="bg-[#ECF9EF] rounded-lg p-3 border border-[#91D69F]">
|
||||
<div class="flex flex-col items-center justify-center h-full w-full">
|
||||
<div class="flex flex-col items-center justify-center w-full h-full text-center">
|
||||
<div class="text-lg text-[#999999] mt-1">小贷: <span class="font-bold text-green-600 text-2xl">{{
|
||||
(recent20SmallLoanRatio * 100).toFixed(0) }}</span> %</div>
|
||||
<div class="font-medium text-[#666666] mb-2">近20次</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近90天 -->
|
||||
<div class="bg-[#ECF9EF] rounded-lg p-3 border border-[#91D69F]">
|
||||
<div class="flex flex-col items-center justify-center h-full w-full">
|
||||
<div class="flex flex-col items-center justify-center w-full h-full text-center">
|
||||
<div class="text-lg text-[#999999] mt-1">金额: <span class="font-bold text-green-600 text-2xl">{{
|
||||
(recent90AmountRatio * 100).toFixed(0) }}</span> %</div>
|
||||
<div class="font-medium text-[#666666] mb-2">近90天</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近50次 -->
|
||||
<div class="bg-[#ECF9EF] rounded-lg p-3 border border-[#91D69F]">
|
||||
<div class="flex flex-col items-center justify-center h-full w-full">
|
||||
<div class="flex flex-col items-center justify-center w-full h-full text-center">
|
||||
<div class="text-lg text-[#999999] mt-1">成功率: <span class="font-bold text-green-600 text-2xl">{{
|
||||
(recent50PaymentRatio * 100).toFixed(0) }}</span> %</div>
|
||||
<div class="font-medium text-[#666666] mb-2">近50次</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近100次 -->
|
||||
<div class="bg-[#ECF9EF] rounded-lg p-3 border border-[#91D69F]">
|
||||
<div class="flex flex-col items-center justify-center h-full w-full">
|
||||
<div class="flex flex-col items-center justify-center w-full h-full text-center">
|
||||
<div class="text-lg text-[#999999] mt-1">成功率: <span class="font-bold text-green-600 text-2xl">{{
|
||||
(recent100PaymentRatio * 100).toFixed(0) }}</span> %</div>
|
||||
<div class="font-medium text-[#666666] mb-2">近100次</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<LRemark
|
||||
content="信用评分系统基于多维度数据建立的综合信用评估模型,提供综合信用指数、分类信用评分和信用等级分析。评分范围为0-1000分,分数越高代表信用状况越好。系统会根据还款表现、借贷历史、负债情况等因素进行评分。建议结合具体业务场景设定信用门槛,并定期更新评分模型以提高预测准确性。" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LTitle from '@/components/LTitle.vue'
|
||||
import LRemark from '@/components/LRemark.vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: 'CreditScores',
|
||||
components: {
|
||||
LTitle,
|
||||
LRemark
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartInstance: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
creditRiskScore() {
|
||||
return parseFloat(this.data.xyp_cpl0081) || 0
|
||||
},
|
||||
amountComplianceIndex() {
|
||||
return parseFloat(this.data.xyp_cpl0082) || 0
|
||||
},
|
||||
countComplianceIndex() {
|
||||
return parseFloat(this.data.xyp_cpl0083) || 0
|
||||
},
|
||||
|
||||
// 模型评分
|
||||
highRiskScore() {
|
||||
const score = parseInt(this.data.xyp_model_score_high)
|
||||
return isNaN(score) || score === -1 ? null : score
|
||||
},
|
||||
midRiskScore() {
|
||||
const score = parseInt(this.data.xyp_model_score_mid)
|
||||
return isNaN(score) || score === -1 ? null : score
|
||||
},
|
||||
lowRiskScore() {
|
||||
const score = parseInt(this.data.xyp_model_score_low)
|
||||
return isNaN(score) || score === -1 ? null : score
|
||||
},
|
||||
|
||||
// 综合信用评分计算
|
||||
creditScoreDisplay() {
|
||||
const avgRisk = (this.creditRiskScore + this.amountComplianceIndex + this.countComplianceIndex) / 3
|
||||
// 风险越高,信用分越低
|
||||
return Math.round((1 - avgRisk) * 850 + 150)
|
||||
},
|
||||
|
||||
creditScoreColor() {
|
||||
if (this.creditScoreDisplay >= 750) return '#1FBE5D'
|
||||
if (this.creditScoreDisplay >= 650) return '#f59e0b'
|
||||
return '#ef4444'
|
||||
},
|
||||
|
||||
// 还款比例计算
|
||||
recent5PaymentRatio() {
|
||||
return parseFloat(this.data.xyp_cpl0074) || 0
|
||||
},
|
||||
recent5AmountRatio() {
|
||||
return parseFloat(this.data.xyp_cpl0073) || 0
|
||||
},
|
||||
recent20PaymentRatio() {
|
||||
return parseFloat(this.data.xyp_t0400002) || 0
|
||||
},
|
||||
recent20SmallLoanRatio() {
|
||||
return parseFloat(this.data.xyp_cpl0075) || 0
|
||||
},
|
||||
recent90DayRatio() {
|
||||
return parseFloat(this.data.xyp_cpl0080) || 0
|
||||
},
|
||||
recent90AmountRatio() {
|
||||
return parseFloat(this.data.xyp_cpl0079) || 0
|
||||
},
|
||||
recent50PaymentRatio() {
|
||||
return parseFloat(this.data.xyp_t0400003) || 0
|
||||
},
|
||||
recent100PaymentRatio() {
|
||||
return parseFloat(this.data.xyp_t0400004) || 0
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initChart()
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.dispose()
|
||||
this.chartInstance = null
|
||||
}
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
|
||||
watch: {
|
||||
creditScoreDisplay() {
|
||||
this.updateChart()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
initChart() {
|
||||
if (!this.$refs.chartRef) return
|
||||
this.chartInstance = echarts.init(this.$refs.chartRef)
|
||||
this.updateChart()
|
||||
},
|
||||
|
||||
updateChart() {
|
||||
if (!this.chartInstance) return
|
||||
|
||||
const scoreColor = this.creditScoreColor
|
||||
const gradientColors = this.getGradientColors()
|
||||
|
||||
const option = {
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
min: 150,
|
||||
max: 1000,
|
||||
radius: '100%',
|
||||
center: ['50%', '80%'],
|
||||
itemStyle: {
|
||||
color: scoreColor,
|
||||
shadowBlur: 6,
|
||||
shadowColor: scoreColor
|
||||
},
|
||||
progress: {
|
||||
show: true,
|
||||
width: 20,
|
||||
roundCap: true,
|
||||
clip: false
|
||||
},
|
||||
axisLine: {
|
||||
roundCap: true,
|
||||
lineStyle: {
|
||||
width: 20,
|
||||
color: [
|
||||
[1, new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: scoreColor + '30' },
|
||||
{ offset: 1, color: scoreColor + '25' }
|
||||
])]
|
||||
]
|
||||
}
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
distance: -30,
|
||||
length: 6,
|
||||
splitNumber: 10,
|
||||
lineStyle: {
|
||||
color: scoreColor,
|
||||
width: 1,
|
||||
opacity: 0.5
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
distance: -36,
|
||||
length: 12,
|
||||
splitNumber: 9,
|
||||
lineStyle: {
|
||||
color: scoreColor,
|
||||
width: 2,
|
||||
opacity: 0.5
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
show: false
|
||||
},
|
||||
anchor: {
|
||||
show: false
|
||||
},
|
||||
pointer: {
|
||||
show: false
|
||||
},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
fontSize: 30,
|
||||
fontWeight: 'bold',
|
||||
color: scoreColor,
|
||||
offsetCenter: [0, 0],
|
||||
formatter: (value) => {
|
||||
return `{value|${value}}{label|信用分}`
|
||||
},
|
||||
rich: {
|
||||
value: {
|
||||
fontSize: 30,
|
||||
fontWeight: 'bold',
|
||||
color: scoreColor,
|
||||
padding: [0, 0, 5, 10]
|
||||
},
|
||||
label: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'normal',
|
||||
color: scoreColor,
|
||||
padding: [0, 0, 0, 5]
|
||||
}
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: this.creditScoreDisplay
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.chartInstance.setOption(option)
|
||||
},
|
||||
|
||||
getGradientColors() {
|
||||
const color = this.creditScoreColor
|
||||
return [
|
||||
{ offset: 0, color: color },
|
||||
{ offset: 1, color: this.lightenColor(color, 0.3) }
|
||||
]
|
||||
},
|
||||
|
||||
lightenColor(color, amount) {
|
||||
const num = parseInt(color.replace('#', ''), 16)
|
||||
const r = Math.min(255, (num >> 16) + amount * 255)
|
||||
const g = Math.min(255, ((num >> 8) & 0x00ff) + amount * 255)
|
||||
const b = Math.min(255, (num & 0x0000ff) + amount * 255)
|
||||
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
|
||||
},
|
||||
|
||||
handleResize() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.resize()
|
||||
}
|
||||
},
|
||||
|
||||
formatModelScore(score) {
|
||||
return score === null ? '未命中' : score.toString()
|
||||
},
|
||||
|
||||
getScoreClass(score) {
|
||||
if (score === null) return 'text-gray-400'
|
||||
if (score >= 750) return 'text-green-600'
|
||||
if (score >= 650) return 'text-yellow-600'
|
||||
return 'text-red-600'
|
||||
},
|
||||
|
||||
getScoreTextClass() {
|
||||
if (this.creditScoreDisplay >= 750) return 'text-green-600'
|
||||
if (this.creditScoreDisplay >= 650) return 'text-yellow-600'
|
||||
return 'text-red-600'
|
||||
},
|
||||
|
||||
getScoreColor() {
|
||||
return this.creditScoreColor
|
||||
},
|
||||
|
||||
getLightScoreColor() {
|
||||
const color = this.creditScoreColor
|
||||
// 将颜色转换为淡色版本(增加透明度)
|
||||
if (color === '#1FBE5D') return '#E8F8F0' // 绿色淡色
|
||||
if (color === '#f59e0b') return '#FEF3C7' // 黄色淡色
|
||||
if (color === '#ef4444') return '#FEE2E2' // 红色淡色
|
||||
return '#F3F4F6' // 默认灰色
|
||||
},
|
||||
|
||||
getModelIcon(score) {
|
||||
// 如果分数为null或未命中,返回灰色图标
|
||||
if (score === null) {
|
||||
return new URL('@/assets/images/report/wmz.png', import.meta.url).href
|
||||
}
|
||||
// 如果命中,返回绿色图标
|
||||
return new URL('@/assets/images/report/zq.png', import.meta.url).href
|
||||
},
|
||||
|
||||
getModelCardClass(score) {
|
||||
// 如果分数为null或未命中,返回未命中样式
|
||||
if (score === null) {
|
||||
return 'bg-[#F0F0F0] rounded-lg p-4 border border-[#D4D4D4] relative'
|
||||
}
|
||||
// 如果命中,返回默认样式
|
||||
return 'bg-gray-50 rounded-lg p-4 border border-gray-200 relative'
|
||||
},
|
||||
|
||||
getCircleStyle(ratio) {
|
||||
let color = '#ef4444'
|
||||
if (ratio >= 0.8) color = '#10b981'
|
||||
else if (ratio >= 0.6) color = '#f59e0b'
|
||||
|
||||
// 确保至少显示10度,让用户知道是图表
|
||||
const minDegree = 10
|
||||
const actualDegree = Math.max(ratio * 360, minDegree)
|
||||
|
||||
return {
|
||||
background: `conic-gradient(${color} ${actualDegree}deg, #e5e7eb 0deg)`
|
||||
}
|
||||
},
|
||||
|
||||
getCreditScoreLevel() {
|
||||
if (this.creditScoreDisplay >= 800) return '优秀'
|
||||
if (this.creditScoreDisplay >= 700) return '良好'
|
||||
if (this.creditScoreDisplay >= 600) return '一般'
|
||||
if (this.creditScoreDisplay >= 500) return '较差'
|
||||
return '很差'
|
||||
},
|
||||
|
||||
getCreditScoreBadgeClass() {
|
||||
if (this.creditScoreDisplay >= 800) return 'bg-[#1FBE5D] text-white'
|
||||
if (this.creditScoreDisplay >= 700) return 'bg-blue-600 text-white'
|
||||
if (this.creditScoreDisplay >= 600) return 'bg-yellow-600 text-white'
|
||||
if (this.creditScoreDisplay >= 500) return 'bg-orange-600 text-white'
|
||||
return 'bg-red-600 text-white'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 组件容器样式 */
|
||||
.credit-scores {
|
||||
background: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.25rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.credit-scores:hover {
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-0.25rem);
|
||||
}
|
||||
|
||||
.section-spacing {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.credit-scores {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.credit-scores {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
372
src/ui/CJRZQ5E9F/components/InstitutionAnalysis.vue
Normal file
372
src/ui/CJRZQ5E9F/components/InstitutionAnalysis.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="rounded-lg border border-[#99999933] mb-4">
|
||||
<div class="pb-4">
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center mb-4 p-4">
|
||||
<div class="w-8 h-8 flex items-center justify-center mr-2">
|
||||
<img src="@/assets/images/report/jgfx.png" alt="机构分析" class="w-8 h-8 object-contain" />
|
||||
</div>
|
||||
<span class="font-bold text-gray-800">机构分析</span>
|
||||
</div>
|
||||
|
||||
<!-- 机构数量统计 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="机构数量统计" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">不同类型贷款机构数量统计</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 消费金融类 -->
|
||||
<div class="bg-[#ECF2FD] rounded-lg p-4 border border-[#CADAF9] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">消费金融类</div>
|
||||
<div class="text-sm text-[#999999]">有场景分期贷款</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5079EA] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(consumerFinanceInstitutions) }} 家机构
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 小贷担保类 -->
|
||||
<div class="bg-[#ECF2FD] rounded-lg p-4 border border-[#CADAF9] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">小贷担保类</div>
|
||||
<div class="text-sm text-[#999999]">现金贷等小额贷款</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5079EA] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(smallLoanSuccessInstitutions) }} 家机构
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网络贷款类 -->
|
||||
<div class="bg-[#ECF2FD] rounded-lg p-4 border border-[#CADAF9] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">网络贷款类</div>
|
||||
<div class="text-sm text-[#999999]">网络现金贷</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5079EA] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(networkLoanInstitutions) }} 家机构
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易金额统计 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="交易金额统计" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">不同时间段的交易金额分析</p>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<div class="mb-4 border border-gray-200 rounded-lg overflow-hidden mx-4">
|
||||
<!-- 表头 -->
|
||||
<div class="bg-[#922D2A] text-white">
|
||||
<div class="grid grid-cols-5 text-sm" style="grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr;">
|
||||
<div class="py-3 px-4 text-left font-semibold border-r border-white whitespace-nowrap">时间段</div>
|
||||
<div class="py-3 px-4 text-center font-semibold border-r border-white whitespace-nowrap">最大</div>
|
||||
<div class="py-3 px-4 text-center font-semibold border-r border-white whitespace-nowrap">最小</div>
|
||||
<div class="py-3 px-4 text-center font-semibold border-r border-white whitespace-nowrap">平均</div>
|
||||
<div class="py-3 px-4 text-center font-semibold whitespace-nowrap">总计</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据行 -->
|
||||
<div class="bg-white">
|
||||
<!-- 近5次 -->
|
||||
<div class="grid grid-cols-5 border-b border-gray-200 text-sm"
|
||||
style="grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr;">
|
||||
<div class="py-3 px-4 font-medium text-gray-800 border-r border-gray-200 whitespace-nowrap">近5次</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent5.max) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent5.min) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent5.avg) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] font-semibold whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent5.sum) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近20次 -->
|
||||
<div class="grid grid-cols-5 border-b border-gray-200 text-sm"
|
||||
style="grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr;">
|
||||
<div class="py-3 px-4 font-medium text-gray-800 border-r border-gray-200 whitespace-nowrap">近20次</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent20.max) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent20.min) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent20.avg) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] font-semibold whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent20.sum) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近50次 -->
|
||||
<div class="grid grid-cols-5 border-b border-gray-200 text-sm"
|
||||
style="grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr;">
|
||||
<div class="py-3 px-4 font-medium text-gray-800 border-r border-gray-200 whitespace-nowrap">近50次</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent50.max) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent50.min) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent50.avg) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] font-semibold whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent50.sum) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 近100次 -->
|
||||
<div class="grid grid-cols-5 text-sm" style="grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr;">
|
||||
<div class="py-3 px-4 font-medium text-gray-800 border-r border-gray-200 whitespace-nowrap">近100次</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent100.max) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent100.min) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] border-r border-gray-200 whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent100.avg) }}
|
||||
</div>
|
||||
<div class="py-3 px-4 text-center text-[#333333] font-semibold whitespace-nowrap">
|
||||
{{ formatAmount(transactionAmountStats.recent100.sum) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 机构风险评估 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="机构风险评估" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">不同风险等级的机构分布</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 高风险 -->
|
||||
<div class="bg-[#FFF0F0] rounded-lg p-4 border border-[#F0CACA] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">高风险</div>
|
||||
<div class="text-sm text-[#999999]">多次失败</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#D44643] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(highRiskInstitutions) }} 家
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中风险 -->
|
||||
<div class="bg-[#FFF8E7] rounded-lg p-4 border border-[#F5D980] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">中风险</div>
|
||||
<div class="text-sm text-[#999999]">偶有失败</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#F5A623] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(mediumRiskInstitutions) }} 家
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 低风险 -->
|
||||
<div class="bg-[#ECF9EF] rounded-lg p-4 border border-[#CAECD3] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">低风险</div>
|
||||
<div class="text-sm text-[#999999]">记录良好</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5EBC62] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(lowRiskInstitutions) }} 家
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<LRemark
|
||||
content="机构分析提供申请人在不同类型金融机构中的借贷表现和风险情况。包括消费金融类、小贷担保类和网络贷款类机构的数量统计和交易金额分析。通过机构分布情况可以了解申请人的借贷偏好和风险集中度。建议关注机构数量过多或单一机构集中度过高的情况,这可能暗示过度借贷或特定风险。" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LTitle from '@/components/LTitle.vue'
|
||||
import LRemark from '@/components/LRemark.vue'
|
||||
|
||||
export default {
|
||||
name: 'InstitutionAnalysis',
|
||||
components: {
|
||||
LTitle,
|
||||
LRemark
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 消费金融类机构数
|
||||
consumerFinanceInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0007)
|
||||
},
|
||||
|
||||
// 小贷担保类成功还款机构数
|
||||
smallLoanSuccessInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_t01degzbc)
|
||||
},
|
||||
|
||||
// 网络贷款类机构数(估算)
|
||||
networkLoanInstitutions() {
|
||||
const total = this.parseIntervalValue(this.data.xyp_cpl0001)
|
||||
const consumer = this.consumerFinanceInstitutions
|
||||
const smallLoan = this.parseIntervalValue(this.data.xyp_cpl0008)
|
||||
return Math.max(0, total - consumer - smallLoan)
|
||||
},
|
||||
|
||||
// 交易金额统计(最大值、最小值、平均值)
|
||||
transactionAmountStats() {
|
||||
return {
|
||||
// 近5次交易
|
||||
recent5: {
|
||||
max: this.parseIntervalValue(this.data.xyp_t01aaizzz),
|
||||
min: this.parseIntervalValue(this.data.xyp_t01abizbz),
|
||||
avg: this.parseIntervalValue(this.data.xyp_t01adizzz),
|
||||
sum: this.parseIntervalValue(this.data.xyp_t01acizzz)
|
||||
},
|
||||
// 近20次交易
|
||||
recent20: {
|
||||
max: this.parseIntervalValue(this.data.xyp_t01aajzzc),
|
||||
min: this.parseIntervalValue(this.data.xyp_t01abjzzc),
|
||||
avg: this.parseIntervalValue(this.data.xyp_t01adjzzc),
|
||||
sum: this.parseIntervalValue(this.data.xyp_t01acjzzz)
|
||||
},
|
||||
// 近50次交易
|
||||
recent50: {
|
||||
max: this.parseIntervalValue(this.data.xyp_t01aakzzz),
|
||||
min: this.parseIntervalValue(this.data.xyp_t01abkzbz),
|
||||
avg: this.parseIntervalValue(this.data.xyp_t01adkzzc),
|
||||
sum: this.parseIntervalValue(this.data.xyp_t01ackzzz)
|
||||
},
|
||||
// 近100次交易
|
||||
recent100: {
|
||||
max: this.parseIntervalValue(this.data.xyp_t01aalzzz),
|
||||
min: this.parseIntervalValue(this.data.xyp_t01ablzbc),
|
||||
avg: this.parseIntervalValue(this.data.xyp_t01adlzzc),
|
||||
sum: this.parseIntervalValue(this.data.xyp_t01aclzzz)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 机构风险评估
|
||||
highRiskInstitutions() {
|
||||
// 基于交易失败机构数估算
|
||||
return this.parseIntervalValue(this.data.xyp_t03td111) || 0
|
||||
},
|
||||
|
||||
mediumRiskInstitutions() {
|
||||
// 基于部分失败机构数估算
|
||||
const total = this.parseIntervalValue(this.data.xyp_cpl0001)
|
||||
const high = this.highRiskInstitutions
|
||||
const low = this.lowRiskInstitutions
|
||||
return Math.max(0, total - high - low)
|
||||
},
|
||||
|
||||
lowRiskInstitutions() {
|
||||
// 基于成功还款机构数
|
||||
return this.parseIntervalValue(this.data.xyp_t01degzzc) || 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseIntervalValue(value) {
|
||||
if (value === null || value === undefined || value === '') return 0
|
||||
const num = parseInt(value)
|
||||
if (isNaN(num)) return 0
|
||||
|
||||
// 根据区间值返回中位数估算
|
||||
switch (num) {
|
||||
case 1: return 2
|
||||
case 2: return 7
|
||||
case 3: return 15
|
||||
case 4: return 25
|
||||
case 5: return 35
|
||||
default: return num
|
||||
}
|
||||
},
|
||||
|
||||
formatMetricValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value < 5) return `${value}`
|
||||
return `${value}+`
|
||||
},
|
||||
|
||||
formatAmount(value) {
|
||||
if (value === 0) return '0元'
|
||||
if (value < 1000) return `${value}元`
|
||||
if (value < 10000) return `${(value / 1000).toFixed(1)}千元`
|
||||
return `${(value / 10000).toFixed(1)}万元`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.institution-analysis {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.section-spacing {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.institution-analysis:hover {
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
.border-b:last-child {
|
||||
@apply border-b-0;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.institution-analysis {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.institution-analysis {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
715
src/ui/CJRZQ5E9F/components/LoanBehaviorAnalysis.vue
Normal file
715
src/ui/CJRZQ5E9F/components/LoanBehaviorAnalysis.vue
Normal file
@@ -0,0 +1,715 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="rounded-lg border border-[#99999933] mb-4">
|
||||
<div class="pb-4">
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center mb-4 p-4">
|
||||
<div class="w-8 h-8 flex items-center justify-center mr-2">
|
||||
<img src="@/assets/images/report/dkxwfx.png" alt="贷款行为分析" class="w-8 h-8 object-contain" />
|
||||
</div>
|
||||
<span class="font-bold text-gray-800">贷款行为分析</span>
|
||||
</div>
|
||||
|
||||
<!-- 机构类型分布 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="机构类型分布" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">不同类型贷款机构数量统计</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 消费金融类 -->
|
||||
<div class="bg-[#ECF2FD] rounded-lg p-4 border border-[#CADAF9] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">消费金融类</div>
|
||||
<div class="text-sm text-[#999999]">有场景分期贷款</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5079EA] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(consumerFinanceInstitutions) }} 家机构
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 小贷担保类 -->
|
||||
<div class="bg-[#ECF2FD] rounded-lg p-4 border border-[#CADAF9] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">小贷担保类</div>
|
||||
<div class="text-sm text-[#999999]">现金贷等小额贷款</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5079EA] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(smallLoanInstitutions) }} 家机构
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 网络贷款类 -->
|
||||
<div class="bg-[#ECF2FD] rounded-lg p-4 border border-[#CADAF9] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">网络贷款类</div>
|
||||
<div class="text-sm text-[#999999]">网络现金贷</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5079EA] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(networkLoanInstitutions) }} 家机构
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 还款表现统计 -->
|
||||
<div class="">
|
||||
<LTitle title="还款表现统计" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">历史还款成功与失败记录</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 历史成功还款 -->
|
||||
<div class="bg-[#ECF9EF] rounded-lg p-4 border border-[#CAECD3] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">历史成功还款</div>
|
||||
<div class="text-sm text-[#999999]">成功还款记录</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5EBC62] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(historicalSuccessPayments) }} 笔
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 历史交易失败 -->
|
||||
<div class="bg-[#F9ECEC] rounded-lg p-4 border border-[#F0CACA] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">历史交易失败</div>
|
||||
<div class="text-sm text-[#999999]">失败交易记录</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#D44643] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ formatMetricValue(historicalFailurePayments) }} 笔
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 整体成功率 -->
|
||||
<div class="bg-[#ECF2FD] rounded-lg p-4 border border-[#CADAF9] relative">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-[#333333] mb-1">整体成功率</div>
|
||||
<div class="text-sm text-[#999999]">还款成功比例</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute top-0 right-0 bg-[#5079EA] text-white px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs">
|
||||
{{ overallSuccessRate }} %
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 时间维度还款分析 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="时间维度还款分析" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">不同时间段的还款成功和失败统计</p>
|
||||
|
||||
<!-- ECharts 图表 -->
|
||||
<div class="mb-6">
|
||||
<div ref="chartRef" :style="{ width: '100%', height: '300px' }"></div>
|
||||
</div>
|
||||
|
||||
<!-- 标签页布局 -->
|
||||
<div class="">
|
||||
<div class="space-y-4">
|
||||
<div class="performance-item">
|
||||
<div class="loan-evaluation-wrap">
|
||||
<!-- 标签页 -->
|
||||
<div class="mb-3">
|
||||
<van-tabs v-model:active="activeTimePeriod" line-width="20" line-height="2"
|
||||
color="var(--color-primary)" class="loan-evaluation-tabs">
|
||||
<van-tab v-for="period in timePeriods" :key="period.name" :name="period.name"
|
||||
:title="period.name" />
|
||||
</van-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 内容显示 -->
|
||||
<div class="loan-evaluation-content">
|
||||
<!-- 总笔数 -->
|
||||
<div class="text-lg text-gray-800 mb-3">
|
||||
总 <span class="font-bold">{{ currentPeriod.total }}</span> 笔
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<!-- 还款成功率 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-gray-600">还款成功率</span>
|
||||
<span class="text-sm font-bold text-gray-800">{{ currentPeriod.successRate.toFixed(1) }}%</span>
|
||||
</div>
|
||||
<div class="h-2 rounded-full" :style="`background-color: ${getSuccessRateLightColor()}`">
|
||||
<div class="h-2 rounded-full transition-all duration-500"
|
||||
:style="`width: ${Math.max(currentPeriod.successRate, 2)}%; background-color: ${getSuccessRateColor()}`">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成功笔数 -->
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-gray-600">成功笔数</span>
|
||||
<span class="text-sm font-bold text-gray-800">{{ currentPeriod.success }} 笔</span>
|
||||
</div>
|
||||
|
||||
<!-- 失败笔数 -->
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-gray-600">失败笔数</span>
|
||||
<span class="text-sm font-bold text-gray-800">{{ currentPeriod.failure }} 笔</span>
|
||||
</div>
|
||||
|
||||
<!-- 成功金额 -->
|
||||
<div class="flex justify-between items-center" v-if="currentPeriod.amounts">
|
||||
<span class="text-sm text-gray-600">成功金额</span>
|
||||
<span class="text-sm font-bold text-gray-800">{{ formatAmount(currentPeriod.amounts.success)
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<!-- 失败金额 -->
|
||||
<div class="flex justify-between items-center" v-if="currentPeriod.amounts">
|
||||
<span class="text-sm text-gray-600">失败金额</span>
|
||||
<span class="text-sm font-bold text-gray-800">{{ formatAmount(currentPeriod.amounts.failure)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 逾期行为分析 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="逾期行为分析" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">各时间段逾期情况统计</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<div class="rounded-xl p-4 relative" :class="getOverdueTimelineCardClass(item.hasOverdue)"
|
||||
v-for="item in overdueTimeline" :key="item.period">
|
||||
<div class="absolute top-0 right-0">
|
||||
<div class="px-2 py-1 text-xs text-white rounded-bl-xl rounded-tr-xl"
|
||||
:class="getOverdueTimelineTagClass(item.hasOverdue)">
|
||||
{{ item.hasOverdue ? '有逾期' : '无逾期' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="w-10 h-10 mr-4">
|
||||
<img :src="getOverdueTimelineIcon(item.hasOverdue)" :alt="item.hasOverdue ? '有逾期' : '无逾期'"
|
||||
class="w-10 h-10 object-contain" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-bold text-gray-800">{{ item.period }}</div>
|
||||
<div class="text-sm text-[#999999] mt-1">{{ item.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前逾期详情 -->
|
||||
<div class="mb-6" v-if="hasCurrentOverdue">
|
||||
<LTitle title="当前逾期提醒" class="mb-2" />
|
||||
<p class="text-gray-400 text-sm mb-4 px-4">需要立即处理的逾期情况</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 px-4">
|
||||
<div class="bg-[#F9ECEC] rounded-lg p-4 border border-[#F0CACA]">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[#999999] text-sm">逾期机构数量</span>
|
||||
<span class="font-bold text-[#D44643] text-lg">{{ formatMetricValue(currentOverdueInstitutions) }}家</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-[#F9ECEC] rounded-lg p-4 border border-[#F0CACA]">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[#999999] text-sm">逾期金额</span>
|
||||
<span class="font-bold text-[#D44643] text-lg">{{ formatAmount(currentOverdueAmount) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<LRemark
|
||||
content="贷款行为分析通过多维度数据展示申请人的借贷行为模式,包括机构类型分布、交易金额分析、申请频率统计和风险事件分析。消费金融类、小贷担保类和网络贷款类机构的数据分别统计,有助于了解申请人的借贷偏好。建议重点关注短期内频繁申请和大额借贷行为,这可能暗示资金紧张或过度借贷风险。分析结果可为风险评估提供重要参考。" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LTitle from '@/components/LTitle.vue'
|
||||
import LRemark from '@/components/LRemark.vue'
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: 'LoanBehaviorAnalysis',
|
||||
components: {
|
||||
LTitle,
|
||||
LRemark
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chartInstance: null,
|
||||
activeTimePeriod: '最近1天'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 平均成功率
|
||||
averageSuccessRate() {
|
||||
if (this.timePeriods.length === 0) return 0
|
||||
const totalRate = this.timePeriods.reduce((sum, period) => sum + period.successRate, 0)
|
||||
return totalRate / this.timePeriods.length
|
||||
},
|
||||
|
||||
// 最高成功率
|
||||
maxSuccessRate() {
|
||||
if (this.timePeriods.length === 0) return 0
|
||||
return Math.max(...this.timePeriods.map(period => period.successRate))
|
||||
},
|
||||
|
||||
// 最低成功率
|
||||
minSuccessRate() {
|
||||
if (this.timePeriods.length === 0) return 0
|
||||
return Math.min(...this.timePeriods.map(period => period.successRate))
|
||||
},
|
||||
|
||||
// 机构类型统计
|
||||
consumerFinanceInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0007)
|
||||
},
|
||||
smallLoanInstitutions() {
|
||||
// 通过总机构数减去消费金融和网络贷款推算
|
||||
const total = this.parseIntervalValue(this.data.xyp_cpl0001)
|
||||
const consumer = this.consumerFinanceInstitutions
|
||||
const network = this.networkLoanInstitutions
|
||||
return Math.max(0, total - consumer - network)
|
||||
},
|
||||
networkLoanInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0008)
|
||||
},
|
||||
|
||||
// 机构类型比例
|
||||
totalInstitutions() {
|
||||
return this.consumerFinanceInstitutions + this.smallLoanInstitutions + this.networkLoanInstitutions || 1
|
||||
},
|
||||
consumerFinanceRatio() {
|
||||
return this.consumerFinanceInstitutions / this.totalInstitutions
|
||||
},
|
||||
smallLoanRatio() {
|
||||
return this.smallLoanInstitutions / this.totalInstitutions
|
||||
},
|
||||
networkLoanRatio() {
|
||||
return this.networkLoanInstitutions / this.totalInstitutions
|
||||
},
|
||||
|
||||
// 还款表现统计
|
||||
historicalSuccessPayments() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0014)
|
||||
},
|
||||
historicalFailurePayments() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0015)
|
||||
},
|
||||
overallSuccessRate() {
|
||||
const total = this.historicalSuccessPayments + this.historicalFailurePayments
|
||||
if (total === 0) return 0
|
||||
return Math.round((this.historicalSuccessPayments / total) * 100)
|
||||
},
|
||||
|
||||
// 时间维度分析
|
||||
timePeriods() {
|
||||
return [
|
||||
{
|
||||
name: '最近1天',
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0017),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0016),
|
||||
amounts: {
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0033),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0032)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '最近7天',
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0019),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0018),
|
||||
amounts: {
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0035),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0034)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '最近14天',
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0021),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0020),
|
||||
amounts: {
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0037),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0036)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '最近21天',
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0064),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0065),
|
||||
amounts: {
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0067),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0066)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '最近30天',
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0023),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0022),
|
||||
amounts: {
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0039),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0038)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '最近90天',
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0025),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0024),
|
||||
amounts: {
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0041),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0040)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '最近180天',
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0027),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0026),
|
||||
amounts: {
|
||||
success: this.parseIntervalValue(this.data.xyp_cpl0043),
|
||||
failure: this.parseIntervalValue(this.data.xyp_cpl0042)
|
||||
}
|
||||
}
|
||||
].map(period => {
|
||||
const total = period.success + period.failure || 1
|
||||
return {
|
||||
...period,
|
||||
total,
|
||||
successRate: (period.success / total) * 100,
|
||||
failureRate: (period.failure / total) * 100
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 逾期时间线
|
||||
overdueTimeline() {
|
||||
return [
|
||||
{
|
||||
period: '最近1天',
|
||||
hasOverdue: this.data.xyp_cpl0028 === '1',
|
||||
description: this.data.xyp_cpl0028 === '1' ? '检测到逾期行为' : '无逾期记录'
|
||||
},
|
||||
{
|
||||
period: '最近7天',
|
||||
hasOverdue: this.data.xyp_cpl0029 === '1',
|
||||
description: this.data.xyp_cpl0029 === '1' ? '检测到逾期行为' : '无逾期记录'
|
||||
},
|
||||
{
|
||||
period: '最近14天',
|
||||
hasOverdue: this.data.xyp_cpl0030 === '1',
|
||||
description: this.data.xyp_cpl0030 === '1' ? '检测到逾期行为' : '无逾期记录'
|
||||
},
|
||||
{
|
||||
period: '最近30天',
|
||||
hasOverdue: this.data.xyp_cpl0031 === '1',
|
||||
description: this.data.xyp_cpl0031 === '1' ? '检测到逾期行为' : '无逾期记录'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 当前逾期状态
|
||||
hasCurrentOverdue() {
|
||||
return this.data.xyp_cpl0044 === '1'
|
||||
},
|
||||
currentOverdueInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0071)
|
||||
},
|
||||
currentOverdueAmount() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0072)
|
||||
},
|
||||
|
||||
// 当前选中的时间段
|
||||
currentPeriod() {
|
||||
return this.timePeriods.find(p => p.name === this.activeTimePeriod) || this.timePeriods[0]
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.initChart()
|
||||
window.addEventListener('resize', this.handleResize)
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.dispose()
|
||||
this.chartInstance = null
|
||||
}
|
||||
window.removeEventListener('resize', this.handleResize)
|
||||
},
|
||||
|
||||
watch: {
|
||||
timePeriods() {
|
||||
this.updateChart()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
initChart() {
|
||||
if (!this.$refs.chartRef) return
|
||||
this.chartInstance = echarts.init(this.$refs.chartRef)
|
||||
this.updateChart()
|
||||
},
|
||||
|
||||
updateChart() {
|
||||
if (!this.chartInstance) return
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
},
|
||||
formatter: (params) => {
|
||||
const data = params[0]
|
||||
const period = this.timePeriods[data.dataIndex]
|
||||
return `${data.name}<br/>成功率: ${period.successRate.toFixed(1)}%<br/>${period.success}/${period.total}笔`
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '15%',
|
||||
top: '5%',
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.timePeriods.map(p => p.name),
|
||||
axisLabel: {
|
||||
rotate: 45,
|
||||
fontSize: 12,
|
||||
color: '#666'
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
max: 100,
|
||||
axisLabel: {
|
||||
formatter: '{value}%',
|
||||
fontSize: 12,
|
||||
color: '#666'
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#e0e0e0'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: '#f0f0f0'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '还款成功率',
|
||||
type: 'bar',
|
||||
data: this.timePeriods.map(p => Math.max(p.successRate, 2)),
|
||||
barWidth: '25%',
|
||||
barMinHeight: 2,
|
||||
itemStyle: {
|
||||
color: '#10b981',
|
||||
borderRadius: [4, 4, 0, 0]
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'top',
|
||||
formatter: (params) => {
|
||||
const period = this.timePeriods[params.dataIndex]
|
||||
return period.successRate > 0 ? `${period.successRate.toFixed(1)}%` : '0%'
|
||||
},
|
||||
fontSize: 11,
|
||||
color: '#333'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.chartInstance.setOption(option)
|
||||
},
|
||||
|
||||
handleResize() {
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.resize()
|
||||
}
|
||||
},
|
||||
|
||||
getSuccessRateColor() {
|
||||
const rate = this.currentPeriod.successRate
|
||||
if (rate >= 80) return '#10b981'
|
||||
if (rate >= 60) return '#f59e0b'
|
||||
return '#ef4444'
|
||||
},
|
||||
|
||||
getSuccessRateLightColor() {
|
||||
const rate = this.currentPeriod.successRate
|
||||
if (rate >= 80) return '#E8F8F0'
|
||||
if (rate >= 60) return '#FEF3C7'
|
||||
return '#FEE2E2'
|
||||
},
|
||||
|
||||
getOverdueTimelineCardClass(hasOverdue) {
|
||||
if (hasOverdue) return 'bg-[#FFF0F0] border border-red-200'
|
||||
return 'bg-[#F0FFF0] border border-green-200'
|
||||
},
|
||||
|
||||
getOverdueTimelineTagClass(hasOverdue) {
|
||||
if (hasOverdue) return 'bg-[#E53935]'
|
||||
return 'bg-[#4CAF50]'
|
||||
},
|
||||
|
||||
getOverdueTimelineIcon(hasOverdue) {
|
||||
if (hasOverdue) return new URL('@/assets/images/report/gfx.png', import.meta.url).href
|
||||
return new URL('@/assets/images/report/zq.png', import.meta.url).href
|
||||
},
|
||||
|
||||
parseIntervalValue(value) {
|
||||
if (!value || value === '' || value === '-1') return 0
|
||||
const num = parseInt(value)
|
||||
if (isNaN(num)) return 0
|
||||
|
||||
// 根据区间映射返回大致范围的中值
|
||||
switch (num) {
|
||||
case 1: return 1
|
||||
case 2: return 3
|
||||
case 3: return 7
|
||||
case 4: return 15
|
||||
case 5: return 25
|
||||
default: return num
|
||||
}
|
||||
},
|
||||
|
||||
formatMetricValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value < 5) return `${value}`
|
||||
return `${value}+`
|
||||
},
|
||||
|
||||
formatAmount(value) {
|
||||
if (value === 0) return '0元'
|
||||
if (value < 1000) return `${value}元`
|
||||
if (value < 10000) return `${(value / 1000).toFixed(1)}千元`
|
||||
return `${(value / 10000).toFixed(1)}万元`
|
||||
},
|
||||
|
||||
getSliceStyle(ratio, startAngle) {
|
||||
const angle = ratio * 360
|
||||
return {
|
||||
'--start-angle': `${startAngle * 360}deg`,
|
||||
'--end-angle': `${(startAngle + ratio) * 360}deg`,
|
||||
'--slice-percent': `${ratio * 100}%`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loan-behavior-analysis {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 24px;
|
||||
margin-bottom: 20px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.section-spacing {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.loan-behavior-analysis:hover {
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* 标签页样式 */
|
||||
.loan-evaluation-tabs {}
|
||||
|
||||
.loan-evaluation-tabs :deep(.van-tabs__wrap) {
|
||||
height: 32px !important;
|
||||
background-color: transparent !important;
|
||||
padding: 0 !important;
|
||||
border-bottom: 1px solid #DDDDDD !important;
|
||||
}
|
||||
|
||||
.loan-evaluation-tabs :deep(.van-tabs__nav) {
|
||||
background-color: transparent !important;
|
||||
gap: 0;
|
||||
height: 32px !important;
|
||||
}
|
||||
|
||||
.loan-evaluation-tabs :deep(.van-tab) {
|
||||
color: #999999 !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.loan-evaluation-tabs :deep(.van-tab--active) {
|
||||
color: var(--van-theme-primary) !important;
|
||||
background-color: unset !important;
|
||||
}
|
||||
|
||||
.loan-evaluation-tabs :deep(.van-tabs__line) {
|
||||
height: 2px !important;
|
||||
border-radius: 1px !important;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
.loan-evaluation-wrap {
|
||||
@apply mx-4 my-1;
|
||||
border: 1px solid #DDDDDD;
|
||||
background-color: #F9F9F9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.loan-evaluation-content {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.loan-behavior-analysis {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.loan-behavior-analysis {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
414
src/ui/CJRZQ5E9F/components/RiskAdvice.vue
Normal file
414
src/ui/CJRZQ5E9F/components/RiskAdvice.vue
Normal file
@@ -0,0 +1,414 @@
|
||||
<template>
|
||||
<div class="rounded-lg border border-[#99999933] mb-4">
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center p-4">
|
||||
<div class="w-8 h-8 flex items-center justify-center mr-2">
|
||||
<img src="@/assets/images/report/zyjy.png" alt="专业建议" class="w-8 h-8 object-contain" />
|
||||
</div>
|
||||
<span class="font-bold text-gray-800">专业建议</span>
|
||||
</div>
|
||||
|
||||
<!-- 风险评估结论 -->
|
||||
<div class="mb-6 px-4">
|
||||
<div class="rounded-xl p-4 relative border" :class="overallRiskLevel.bgClass">
|
||||
<!-- 风险分标签 -->
|
||||
<div
|
||||
class="absolute top-0 right-0 px-3 py-1 rounded-bl-lg rounded-tr-lg text-sm font-bold text-white whitespace-nowrap"
|
||||
:class="getRiskBadgeClass()">
|
||||
风险分:{{ overallRiskScore }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4 mb-3">
|
||||
<div class="w-10 h-10 flex-shrink-0">
|
||||
<img :src="getRiskIcon()" :alt="overallRiskLevel.title" class="w-10 h-10 object-contain" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<h3 class="text-base font-bold text-[#333333]">{{ overallRiskLevel.title }}</h3>
|
||||
<p class="text-sm text-[#999999]">{{ overallRiskLevel.subtitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-[#333333] leading-relaxed">
|
||||
{{ overallRiskLevel.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键建议 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="关键建议" class="mb-2" />
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<div class="rounded-xl p-4 relative" v-for="recommendation in keyRecommendations" :key="recommendation.id"
|
||||
:class="getRecommendationCardClass(recommendation.priority)">
|
||||
<!-- 优先级标签 -->
|
||||
<div class="absolute top-0 right-0 px-2 py-1 rounded-bl-lg rounded-tr-lg text-xs font-bold text-white"
|
||||
:class="getRecommendationBadgeClass(recommendation.priority)">
|
||||
{{ recommendation.priorityText }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 flex-shrink-0">
|
||||
<img :src="getRecommendationIcon(recommendation.priority)" :alt="recommendation.title"
|
||||
class="w-10 h-10 object-contain" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h4 class="text-base font-bold text-[#333333] mb-2">{{ recommendation.title }}</h4>
|
||||
<p class="text-sm text-[#999999] mb-3 leading-relaxed">{{ recommendation.description }}</p>
|
||||
<div class="flex flex-wrap gap-2" v-if="recommendation.actions.length > 0">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-xl text-sm"
|
||||
:class="getRecommendationActionClass(recommendation.priority)"
|
||||
v-for="action in recommendation.actions.slice(0, 3)" :key="action">
|
||||
{{ action }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<!-- <div class="px-4 pb-4">
|
||||
<LRemark
|
||||
content="专业建议基于综合风险评估结果,为不同风险等级的申请人提供针对性的审核廊议和风险管控措施。建议内容包括关键建议、风险管控措施、注意事项和后续跟进等方面。系统会根据当前风险等级动态调整建议内容,但最终决策仍需结合具体业务情况和风险政策进行综合判断。建议定期复评风险状况和调整风险管控策略。" />
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LTitle from '@/components/LTitle.vue'
|
||||
import LRemark from '@/components/LRemark.vue'
|
||||
|
||||
export default {
|
||||
name: 'RiskAdvice',
|
||||
components: {
|
||||
LTitle,
|
||||
LRemark
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 综合风险评估
|
||||
overallRiskScore() {
|
||||
const creditScore = parseFloat(this.data.xyp_cpl0081) || 0
|
||||
const amountIndex = parseFloat(this.data.xyp_cpl0082) || 0
|
||||
const countIndex = parseFloat(this.data.xyp_cpl0083) || 0
|
||||
|
||||
// 风险分数 (0-100, 分数越高风险越低)
|
||||
const avgRisk = (creditScore + amountIndex + countIndex) / 3
|
||||
return Math.round((1 - avgRisk) * 100)
|
||||
},
|
||||
|
||||
overallRiskLevel() {
|
||||
const score = this.overallRiskScore
|
||||
const hasCurrentOverdue = this.data.xyp_cpl0044 === '1'
|
||||
const hasRecentOverdue = this.data.xyp_cpl0028 === '1' || this.data.xyp_cpl0029 === '1'
|
||||
|
||||
if (hasCurrentOverdue || score < 30) {
|
||||
return {
|
||||
title: '高风险用户',
|
||||
subtitle: '需要立即关注',
|
||||
description: '当前信用状况较差,建议立即处理逾期问题并暂停新申请。',
|
||||
bgClass: 'bg-red-50 border-red-200',
|
||||
iconBg: 'bg-red-500',
|
||||
iconComponent: 'ExclamationTriangleIcon'
|
||||
}
|
||||
} else if (hasRecentOverdue || score < 60) {
|
||||
return {
|
||||
title: '中风险用户',
|
||||
subtitle: '需要改善',
|
||||
description: '信用状况一般,建议优化还款表现并控制申请频率。',
|
||||
bgClass: 'bg-yellow-50 border-yellow-200',
|
||||
iconBg: 'bg-yellow-500',
|
||||
iconComponent: 'ExclamationCircleIcon'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
title: '低风险用户',
|
||||
subtitle: '状况良好',
|
||||
description: '信用状况良好,建议继续保持良好的还款习惯。',
|
||||
bgClass: 'bg-green-50 border-green-200',
|
||||
iconBg: 'bg-green-500',
|
||||
iconComponent: 'CheckCircleIcon'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 关键建议
|
||||
keyRecommendations() {
|
||||
const recommendations = []
|
||||
|
||||
// 当前逾期处理
|
||||
if (this.data.xyp_cpl0044 === '1') {
|
||||
recommendations.push({
|
||||
id: 'handle_overdue',
|
||||
title: '立即处理逾期',
|
||||
description: '尽快联系机构协商还款,避免影响征信。',
|
||||
priority: 'urgent',
|
||||
priorityText: '紧急',
|
||||
iconComponent: 'ExclamationTriangleIcon',
|
||||
iconBg: 'bg-red-500',
|
||||
borderClass: 'border-l-red-500',
|
||||
badgeClass: 'bg-red-100 text-red-800',
|
||||
actions: [
|
||||
'联系机构协商',
|
||||
'优先还小额',
|
||||
'制定还款计划'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 高频申请警告
|
||||
const recent1Day = this.parseIntervalValue(this.data.xyp_cpl0070)
|
||||
const recent7Day = this.parseIntervalValue(this.data.xyp_cpl0009)
|
||||
if (recent1Day > 0 || recent7Day > 5) {
|
||||
recommendations.push({
|
||||
id: 'reduce_applications',
|
||||
title: '控制申请频率',
|
||||
description: '近期申请过频,建议暂停新申请3-6个月。',
|
||||
priority: 'high',
|
||||
priorityText: '重要',
|
||||
iconComponent: 'PauseCircleIcon',
|
||||
iconBg: 'bg-orange-500',
|
||||
borderClass: 'border-l-orange-500',
|
||||
badgeClass: 'bg-orange-100 text-orange-800',
|
||||
actions: [
|
||||
'暂停新申请',
|
||||
'整理现有贷款',
|
||||
'制定资金规划'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 还款表现改善
|
||||
const successRate = parseFloat(this.data.xyp_cpl0080) || 0
|
||||
const recent5SuccessRate = parseFloat(this.data.xyp_cpl0074) || 0
|
||||
const recent20SuccessRate = parseFloat(this.data.xyp_t0400002) || 0
|
||||
|
||||
if (successRate < 0.8 || recent5SuccessRate < 0.8 || recent20SuccessRate < 0.8) {
|
||||
recommendations.push({
|
||||
id: 'improve_repayment',
|
||||
title: '提升还款表现',
|
||||
description: `还款成功率偏低,建议设置自动还款。`,
|
||||
priority: 'high',
|
||||
priorityText: '重要',
|
||||
iconComponent: 'CalendarIcon',
|
||||
iconBg: 'bg-blue-500',
|
||||
borderClass: 'border-l-blue-500',
|
||||
badgeClass: 'bg-blue-100 text-blue-800',
|
||||
actions: [
|
||||
'设置自动还款',
|
||||
'确保账户余额',
|
||||
'按时还款'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 机构数量管理
|
||||
const totalInstitutions = this.parseIntervalValue(this.data.xyp_cpl0001)
|
||||
if (totalInstitutions > 10) {
|
||||
recommendations.push({
|
||||
id: 'manage_institutions',
|
||||
title: '优化机构数量',
|
||||
description: '机构数量较多,建议优先结清小额贷款。',
|
||||
priority: 'medium',
|
||||
priorityText: '建议',
|
||||
iconComponent: 'AdjustmentsIcon',
|
||||
iconBg: 'bg-purple-500',
|
||||
borderClass: 'border-l-purple-500',
|
||||
badgeClass: 'bg-purple-100 text-purple-800',
|
||||
actions: [
|
||||
'结清小额贷款',
|
||||
'合并同类贷款',
|
||||
'控制新增机构'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 交易失败后恢复分析
|
||||
const consumerFailureRecoveryDays = this.parseIntervalValue(this.data.xyp_cpl0054)
|
||||
const smallLoanFailureRecoveryDays = this.parseIntervalValue(this.data.xyp_cpl0055)
|
||||
const overallFailureRecoveryDays = this.parseIntervalValue(this.data.xyp_cpl0056)
|
||||
|
||||
if (consumerFailureRecoveryDays > 30 || smallLoanFailureRecoveryDays > 30 || overallFailureRecoveryDays > 30) {
|
||||
recommendations.push({
|
||||
id: 'improve_recovery_time',
|
||||
title: '快速恢复能力',
|
||||
description: '失败后恢复较慢,建议建立应急资金。',
|
||||
priority: 'medium',
|
||||
priorityText: '建议',
|
||||
iconComponent: 'ClockIcon',
|
||||
iconBg: 'bg-indigo-500',
|
||||
borderClass: 'border-l-indigo-500',
|
||||
badgeClass: 'bg-indigo-100 text-indigo-800',
|
||||
actions: [
|
||||
'建立应急资金',
|
||||
'优化资金流',
|
||||
'快速处理失败'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 信用修复
|
||||
const settledInstitutions = this.parseIntervalValue(this.data.xyp_cpl0002)
|
||||
if (settledInstitutions > 0) {
|
||||
recommendations.push({
|
||||
id: 'credit_repair',
|
||||
title: '继续信用修复',
|
||||
description: '已有良好结清记录,建议继续保持。',
|
||||
priority: 'medium',
|
||||
priorityText: '建议',
|
||||
iconComponent: 'TrendingUpIcon',
|
||||
iconBg: 'bg-green-500',
|
||||
borderClass: 'border-l-green-500',
|
||||
badgeClass: 'bg-green-100 text-green-800',
|
||||
actions: [
|
||||
'保持还款记录',
|
||||
'结清剩余贷款',
|
||||
'稳定收入来源'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
return recommendations
|
||||
},
|
||||
|
||||
// 改善步骤
|
||||
improvementSteps() {
|
||||
const steps = []
|
||||
|
||||
if (this.data.xyp_cpl0044 === '1') {
|
||||
steps.push({
|
||||
id: 'immediate_action',
|
||||
title: '立即行动期',
|
||||
description: '处理逾期问题,停止新申请',
|
||||
duration: '1-2周',
|
||||
impact: '高',
|
||||
badgeClass: 'bg-red-100 text-red-800'
|
||||
})
|
||||
}
|
||||
|
||||
steps.push({
|
||||
id: 'stabilization',
|
||||
title: '稳定期',
|
||||
description: '建立稳定还款计划,按时还款',
|
||||
duration: '3-6个月',
|
||||
impact: '中',
|
||||
badgeClass: 'bg-yellow-100 text-yellow-800'
|
||||
})
|
||||
|
||||
steps.push({
|
||||
id: 'optimization',
|
||||
title: '优化期',
|
||||
description: '减少机构数量,优化债务结构',
|
||||
duration: '6-12个月',
|
||||
impact: '中',
|
||||
badgeClass: 'bg-yellow-100 text-yellow-800'
|
||||
})
|
||||
|
||||
steps.push({
|
||||
id: 'recovery',
|
||||
title: '恢复期',
|
||||
description: '建立良好信用记录,恢复信用状况',
|
||||
duration: '12-24个月',
|
||||
impact: '高',
|
||||
badgeClass: 'bg-green-100 text-green-800'
|
||||
})
|
||||
|
||||
return steps
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
parseIntervalValue(value) {
|
||||
if (!value || value === '' || value === '-1') return 0
|
||||
const num = parseInt(value)
|
||||
if (isNaN(num)) return 0
|
||||
|
||||
// 根据区间映射返回大致范围的中值
|
||||
switch (num) {
|
||||
case 1: return 1
|
||||
case 2: return 3
|
||||
case 3: return 7
|
||||
case 4: return 15
|
||||
case 5: return 25
|
||||
default: return num
|
||||
}
|
||||
},
|
||||
|
||||
getRiskIcon() {
|
||||
// 根据风险等级返回对应的图标
|
||||
if (this.overallRiskLevel.iconComponent === 'ExclamationTriangleIcon') {
|
||||
return new URL('@/assets/images/report/gfx.png', import.meta.url).href
|
||||
} else if (this.overallRiskLevel.iconComponent === 'ExclamationCircleIcon') {
|
||||
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
|
||||
} else {
|
||||
return new URL('@/assets/images/report/zq.png', import.meta.url).href
|
||||
}
|
||||
},
|
||||
|
||||
getRiskBadgeClass() {
|
||||
// 根据风险等级返回徽章样式
|
||||
if (this.overallRiskLevel.iconComponent === 'ExclamationTriangleIcon') {
|
||||
return 'bg-[#D44643]'
|
||||
} else if (this.overallRiskLevel.iconComponent === 'ExclamationCircleIcon') {
|
||||
return 'bg-[#F5A623]'
|
||||
} else {
|
||||
return 'bg-[#5EBC62]'
|
||||
}
|
||||
},
|
||||
|
||||
getRecommendationCardClass(priority) {
|
||||
// 根据优先级返回卡片样式
|
||||
if (priority === 'urgent') {
|
||||
return 'bg-[#FFF0F0] border border-[#F0CACA]'
|
||||
} else if (priority === 'high') {
|
||||
return 'bg-[#ECF2FD] border border-[#CADAF9]'
|
||||
} else {
|
||||
return 'bg-[#F0FFF0] border border-green-200'
|
||||
}
|
||||
},
|
||||
|
||||
getRecommendationIcon(priority) {
|
||||
// 根据优先级返回图标
|
||||
if (priority === 'urgent') {
|
||||
return new URL('@/assets/images/report/gfx.png', import.meta.url).href
|
||||
} else if (priority === 'high') {
|
||||
return new URL('@/assets/images/report/wxts_icon.png', import.meta.url).href
|
||||
} else {
|
||||
return new URL('@/assets/images/report/zq.png', import.meta.url).href
|
||||
}
|
||||
},
|
||||
|
||||
getRecommendationBadgeClass(priority) {
|
||||
// 根据优先级返回徽章样式
|
||||
if (priority === 'urgent') {
|
||||
return 'bg-[#D44643]'
|
||||
} else if (priority === 'high') {
|
||||
return 'bg-[#5079EA]'
|
||||
} else {
|
||||
return 'bg-[#5EBC62]'
|
||||
}
|
||||
},
|
||||
|
||||
getRecommendationActionClass(priority) {
|
||||
// 根据优先级返回操作按钮样式
|
||||
if (priority === 'urgent') {
|
||||
return 'bg-[#F0CACA] text-[#D44643]'
|
||||
} else if (priority === 'high') {
|
||||
return 'bg-[#DBE6FC] text-[#2B79EE]'
|
||||
} else {
|
||||
return 'bg-green-200 text-[#5EBC62]'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 组件样式已使用 Tailwind CSS */
|
||||
</style>
|
||||
638
src/ui/CJRZQ5E9F/components/RiskIndicators.vue
Normal file
638
src/ui/CJRZQ5E9F/components/RiskIndicators.vue
Normal file
@@ -0,0 +1,638 @@
|
||||
<template>
|
||||
<div class="rounded-lg border border-[#99999933] mb-4">
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center mb-4 p-4">
|
||||
<div class="w-8 h-8 flex items-center justify-center mr-2">
|
||||
<img src="@/assets/images/report/fxzbxq.png" alt="风险指标详情" class="w-8 h-8 object-contain" />
|
||||
</div>
|
||||
<span class="font-bold text-gray-800">风险指标详情</span>
|
||||
</div>
|
||||
|
||||
<!-- 核心风险指标 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="核心风险指标" class="mb-2" />
|
||||
<p class="text-xs text-[#999999] px-4 mb-3">关键风险评估指标汇总</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 警示指标 -->
|
||||
<div class="bg-[#F9F5ED] border border-[#F0E2CB] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] border-b border-[#F0E2CB] px-4 py-2">警示指标</h4>
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">最近1天申请机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatMetricValue(recent1DayInstitutions) }}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">最近7天申请机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatMetricValue(recent7DayInstitutions) }}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">履约金额指数</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ (amountComplianceIndex * 100).toFixed(0) }}%</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">履约笔数指数</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ (countComplianceIndex * 100).toFixed(0) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 正面指标 -->
|
||||
<div class="bg-[#ECF9EF] border border-[#CAECD3] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#CAECD3]">正面指标</h4>
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">已结清机构数</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatMetricValue(settledInstitutions) }}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">信用贷款时长</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatDays(creditLoanDuration) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">历史成功还款</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatMetricValue(historicalSuccessPayments)
|
||||
}}笔</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易失败后还款分析 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="交易失败后还款分析" class="mb-2" />
|
||||
<p class="text-xs text-[#999999] px-4 mb-3">失败后的恢复能力评估</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 失败后还款次数 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">失败后还款次数</h4>
|
||||
<div class="space-y-3 p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-sm text-[#999999] min-w-20">已结清机构数</span>
|
||||
<div class="flex-1 h-2 bg-[#E3EFFD] rounded-full overflow-hidden">
|
||||
<div class="h-full bg-[#2B79EE] rounded-full transition-all duration-300"
|
||||
:style="`width: ${Math.max(getRecoveryPercentage(consumerFailureRecovery), 2)}%`"></div>
|
||||
</div>
|
||||
<span class="text-base font-bold text-[#333333] min-w-12 text-right">{{
|
||||
formatMetricValue(consumerFailureRecovery) }}次</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-sm text-[#999999] min-w-20">信用贷款时长</span>
|
||||
<div class="flex-1 h-2 bg-[#E3EFFD] rounded-full overflow-hidden">
|
||||
<div class="h-full bg-[#2B79EE] rounded-full transition-all duration-300"
|
||||
:style="`width: ${Math.max(getRecoveryPercentage(smallLoanFailureRecovery), 2)}%`"></div>
|
||||
</div>
|
||||
<span class="text-base font-bold text-[#333333] min-w-12 text-right">{{
|
||||
formatMetricValue(smallLoanFailureRecovery) }}次</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-sm text-[#999999] min-w-20">历史成功还款</span>
|
||||
<div class="flex-1 h-2 bg-[#E3EFFD] rounded-full overflow-hidden">
|
||||
<div class="h-full bg-[#2B79EE] rounded-full transition-all duration-300"
|
||||
:style="`width: ${Math.max(getRecoveryPercentage(overallFailureRecovery), 2)}%`"></div>
|
||||
</div>
|
||||
<span class="text-base font-bold text-[#333333] min-w-12 text-right">{{
|
||||
formatMetricValue(overallFailureRecovery) }}次</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 恢复时间分析 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">恢复时间分析</h4>
|
||||
<div class="p-4">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-sm text-[#999999]">平均恢复时间</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatDays(avgRecoveryTime) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm text-[#999999]">
|
||||
<span>最短: {{ formatDays(minRecoveryTime) }}</span>
|
||||
<span>最长: {{ formatDays(maxRecoveryTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 比例指标分析 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="比例指标分析" class="mb-2" />
|
||||
<p class="text-xs text-[#999999] px-4 mb-3">各类交易行为的比例统计</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 金额比例指标 -->
|
||||
<div class="text-base font-bold text-[#333333]">金额比例指标</div>
|
||||
<div class="space-y-3">
|
||||
<div v-for="item in amountRatios" :key="item.id">
|
||||
<div class="bg-[#ECF2FD] border border-[#CADAF9] rounded-xl p-4">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-base font-bold text-[#333333]">{{ item.name }}</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ (item.ratio * 100).toFixed(1) }}%</span>
|
||||
</div>
|
||||
<div class="h-2 bg-[#DBE6FC] rounded-full overflow-hidden mb-1.5">
|
||||
<div class="h-full rounded-full transition-all duration-300"
|
||||
:style="`width: ${Math.max(item.ratio * 100, 2)}%; background-color: #5079EA;`"></div>
|
||||
</div>
|
||||
<div class="text-sm text-[#999999]">{{ item.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 笔数比例指标 -->
|
||||
<div class="text-base font-bold text-[#333333]">笔数比例指标</div>
|
||||
<div class="space-y-3">
|
||||
<div v-for="item in countRatios" :key="item.id">
|
||||
<div class="rounded-xl p-4" :class="getRatioCardClass(item.id)">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-base font-bold text-[#333333]">{{ item.name }}</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ (item.ratio * 100).toFixed(1) }}%</span>
|
||||
</div>
|
||||
<div class="h-2 rounded-full overflow-hidden mb-1.5" :class="getRatioBarBgClass(item.id)">
|
||||
<div class="h-full rounded-full transition-all duration-300"
|
||||
:style="`width: ${Math.max(item.ratio * 100, 2)}%; background-color: ${getRatioBarColor(item.id)};`">
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-[#999999]">{{ item.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 特殊指标 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="特殊指标" class="mb-2" />
|
||||
<p class="text-xs text-[#999999] px-4 mb-3">其他重要风险评估指标</p>
|
||||
|
||||
<div class="space-y-3 px-4">
|
||||
<!-- 时间相关指标 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">时间相关指标</h4>
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">最近一次交易距今</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatDays(lastTransactionDays) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">最近一次还款距今</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatDays(lastRepaymentDays) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">信用贷款总时长</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatDays(creditLoanDuration) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易失败机构 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">交易失败机构</h4>
|
||||
<div class="p-4">
|
||||
<div class="space-y-2" v-for="item in failureInstitutionTimeline" :key="item.period">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">{{ item.period }}</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatMetricValue(item.count) }}家</span>
|
||||
</div>
|
||||
<div class="h-2 bg-[#E3EFFD] rounded-full overflow-hidden">
|
||||
<div class="h-full rounded-full transition-all duration-300 bg-[#2B79EE]"
|
||||
:style="`width: ${Math.max(item.percentage, 2)}%`"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增机构比例 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">新增机构比例</h4>
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center" v-for="item in newInstitutionRatios" :key="item.period">
|
||||
<span class="text-sm text-[#999999]">{{ item.period }}</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ (item.ratio * 100).toFixed(0) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 交易金额统计 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">交易金额统计</h4>
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">近90天最大交易金额</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatAmount(parseIntervalValue(data.xyp_t01aafzzz))
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">近90天最小交易金额</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatAmount(parseIntervalValue(data.xyp_t01abfzzz))
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">近90天平均交易金额</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{ formatAmount(parseIntervalValue(data.xyp_t01adfzzz))
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 机构去重统计 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">机构去重统计</h4>
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">近20次交易还款成功机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{
|
||||
formatMetricValue(parseIntervalValue(data.xyp_t01dejzzc)) }}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">近50次交易还款成功机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{
|
||||
formatMetricValue(parseIntervalValue(data.xyp_t01dekzzc)) }}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">近100次交易还款成功机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{
|
||||
formatMetricValue(parseIntervalValue(data.xyp_t01delzzc)) }}家</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 特殊风险指标 -->
|
||||
<div class="bg-[#F9F9F9] border border-[#EEEEEE] rounded-xl">
|
||||
<h4 class="text-base font-bold text-[#333333] px-4 py-2 border-b border-[#EEEEEE]">特殊风险指标</h4>
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">最近90天交易失败机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{
|
||||
formatMetricValue(parseIntervalValue(data.xyp_t03td111))
|
||||
}}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">最近180天交易失败机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{
|
||||
formatMetricValue(parseIntervalValue(data.xyp_t03td115))
|
||||
}}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-sm text-[#999999]">最近一次交易为失败机构</span>
|
||||
<span class="text-base font-bold text-[#333333]">{{
|
||||
formatMetricValue(parseIntervalValue(data.xyp_t03td148))
|
||||
}}家</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<div class="px-4 pb-4">
|
||||
<LRemark
|
||||
content="风险指标详情提供全面的风险评估指标分析,包括核心风险指标、警示指标和风险分布统计。核心风险指标包括当前逾期、近期逾期和信用风险评分等严重风险项目。警示指标涵盖申请频率、机构数量等预警信息。建议重点关注严重风险指标,及时评估申请人的还款能力和信用状况。指标数据基于多维度风险模型计算,具有较高的预测准确性。" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LTitle from '@/components/LTitle.vue'
|
||||
import LRemark from '@/components/LRemark.vue'
|
||||
|
||||
export default {
|
||||
name: 'RiskIndicators',
|
||||
components: {
|
||||
LTitle,
|
||||
LRemark
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 核心风险指标
|
||||
hasCurrentOverdue() {
|
||||
return this.data.xyp_cpl0044 === '1'
|
||||
},
|
||||
currentOverdueInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0071)
|
||||
},
|
||||
hasRecentOverdue() {
|
||||
return this.data.xyp_cpl0028 === '1' || this.data.xyp_cpl0029 === '1' || this.data.xyp_cpl0030 === '1'
|
||||
},
|
||||
creditRiskScore() {
|
||||
return parseFloat(this.data.xyp_cpl0081) || 0
|
||||
},
|
||||
highCreditRisk() {
|
||||
return this.creditRiskScore > 0.7
|
||||
},
|
||||
hasCriticalRisk() {
|
||||
return this.hasCurrentOverdue || this.hasRecentOverdue || this.highCreditRisk
|
||||
},
|
||||
|
||||
// 警示指标
|
||||
recent1DayInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0070)
|
||||
},
|
||||
recent7DayInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0009)
|
||||
},
|
||||
amountComplianceIndex() {
|
||||
return parseFloat(this.data.xyp_cpl0082) || 0
|
||||
},
|
||||
countComplianceIndex() {
|
||||
return parseFloat(this.data.xyp_cpl0083) || 0
|
||||
},
|
||||
|
||||
// 正面指标
|
||||
settledInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0002)
|
||||
},
|
||||
creditLoanDuration() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0045)
|
||||
},
|
||||
historicalSuccessPayments() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0014)
|
||||
},
|
||||
|
||||
// 交易失败后还款分析
|
||||
consumerFailureRecovery() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0052)
|
||||
},
|
||||
smallLoanFailureRecovery() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0053)
|
||||
},
|
||||
overallFailureRecovery() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0069)
|
||||
},
|
||||
|
||||
// 恢复时间分析
|
||||
avgRecoveryTime() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0062)
|
||||
},
|
||||
minRecoveryTime() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0059)
|
||||
},
|
||||
maxRecoveryTime() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0056)
|
||||
},
|
||||
|
||||
// 金额比例指标
|
||||
amountRatios() {
|
||||
return [
|
||||
{
|
||||
id: 'recent_90_amount_ratio',
|
||||
name: '近90天还款成功金额比例',
|
||||
ratio: parseFloat(this.data.xyp_t02acfzbc_acfzbz) || 0,
|
||||
description: '小贷担保类近90天还款成功金额占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_180_amount_ratio',
|
||||
name: '近180天还款成功金额比例',
|
||||
ratio: parseFloat(this.data.xyp_t02acgzbc_acgzbz) || 0,
|
||||
description: '小贷担保类近180天还款成功金额占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_360_amount_ratio',
|
||||
name: '近360天还款成功金额比例',
|
||||
ratio: parseFloat(this.data.xyp_t02achzbc_achzbz) || 0,
|
||||
description: '小贷担保类近360天还款成功金额占比'
|
||||
},
|
||||
{
|
||||
id: 'failure_amount_ratio',
|
||||
name: '交易失败金额比例',
|
||||
ratio: parseFloat(this.data.xyp_t02aczzza_aczzzz) || 0,
|
||||
description: '因交易能力不足导致失败的金额占总交易金额比例'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 笔数比例指标
|
||||
countRatios() {
|
||||
return [
|
||||
{
|
||||
id: 'recent_90_count_ratio',
|
||||
name: '近90天还款成功笔数比例',
|
||||
ratio: parseFloat(this.data.xyp_t02ccfzbc_ccfzbz) || 0,
|
||||
description: '小贷担保类近90天还款成功笔数占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_180_count_ratio',
|
||||
name: '近180天还款成功笔数比例',
|
||||
ratio: parseFloat(this.data.xyp_t02ccgzbc_ccgzbz) || 0,
|
||||
description: '小贷担保类近180天还款成功笔数占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_360_count_ratio',
|
||||
name: '近360天还款成功笔数比例',
|
||||
ratio: parseFloat(this.data.xyp_t02cchzbc_cchzbz) || 0,
|
||||
description: '小贷担保类近360天还款成功笔数占比'
|
||||
},
|
||||
{
|
||||
id: 'overall_success_ratio',
|
||||
name: '总体还款成功率',
|
||||
ratio: parseFloat(this.data.xyp_t02cczzzc_cczzzz) || 0,
|
||||
description: '历史总体还款成功笔数占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_5_failure_ratio',
|
||||
name: '近5次交易失败比例',
|
||||
ratio: parseFloat(this.data.xyp_t02ccizza_cczzza) || 0,
|
||||
description: '近5次交易中因交易能力不足导致失败的笔数占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_30_failure_ratio',
|
||||
name: '近30天交易失败比例',
|
||||
ratio: parseFloat(this.data.xyp_t02ccezza_cczzza) || 0,
|
||||
description: '近30天因交易能力不足导致失败的笔数占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_90_failure_ratio',
|
||||
name: '近90天交易失败比例',
|
||||
ratio: parseFloat(this.data.xyp_t02ccfzza_cczzza) || 0,
|
||||
description: '近90天因交易能力不足导致失败的笔数占比'
|
||||
},
|
||||
{
|
||||
id: 'recent_180_failure_ratio',
|
||||
name: '近180天交易失败比例',
|
||||
ratio: parseFloat(this.data.xyp_t02ccgzza_ccgzzz) || 0,
|
||||
description: '近180天因交易能力不足导致失败的笔数占比'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 特殊指标
|
||||
lastTransactionDays() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0046)
|
||||
},
|
||||
lastRepaymentDays() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0068)
|
||||
},
|
||||
|
||||
// 交易失败机构时间线
|
||||
failureInstitutionTimeline() {
|
||||
const items = [
|
||||
{ period: '7天', count: this.parseIntervalValue(this.data.xyp_cpl0048) },
|
||||
{ period: '14天', count: this.parseIntervalValue(this.data.xyp_cpl0049) },
|
||||
{ period: '21天', count: this.parseIntervalValue(this.data.xyp_cpl0050) },
|
||||
{ period: '30天', count: this.parseIntervalValue(this.data.xyp_cpl0051) },
|
||||
{ period: '90天', count: this.parseIntervalValue(this.data.xyp_t03td045) },
|
||||
{ period: '180天', count: this.parseIntervalValue(this.data.xyp_t03td053) }
|
||||
]
|
||||
|
||||
const maxCount = Math.max(...items.map(item => item.count)) || 1
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
percentage: (item.count / maxCount) * 100
|
||||
}))
|
||||
},
|
||||
|
||||
// 新增机构比例
|
||||
newInstitutionRatios() {
|
||||
return [
|
||||
{
|
||||
period: '30天',
|
||||
ratio: parseFloat(this.data.xyp_t02dezezz_dezzzz) || 0
|
||||
},
|
||||
{
|
||||
period: '90天',
|
||||
ratio: parseFloat(this.data.xyp_t02dezfzz_dezzzz) || 0
|
||||
},
|
||||
{
|
||||
period: '180天',
|
||||
ratio: parseFloat(this.data.xyp_t02dezgzz_dezzzz) || 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
parseIntervalValue(value) {
|
||||
if (!value || value === '' || value === '-1') return 0
|
||||
const num = parseInt(value)
|
||||
if (isNaN(num)) return 0
|
||||
|
||||
// 根据区间映射返回大致范围的中值
|
||||
switch (num) {
|
||||
case 1: return 1
|
||||
case 2: return 3
|
||||
case 3: return 7
|
||||
case 4: return 15
|
||||
case 5: return 25
|
||||
default: return num
|
||||
}
|
||||
},
|
||||
|
||||
formatMetricValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value < 5) return `${value}`
|
||||
return `${value}+`
|
||||
},
|
||||
|
||||
formatDays(value) {
|
||||
if (value === 0) return '无记录'
|
||||
if (value < 30) return `${value}天`
|
||||
if (value < 365) return `${Math.floor(value / 30)}个月`
|
||||
return `${Math.floor(value / 365)}年`
|
||||
},
|
||||
|
||||
formatAmount(value) {
|
||||
if (value === 0) return '0元'
|
||||
if (value < 1000) return `${value}元`
|
||||
if (value < 10000) return `${(value / 1000).toFixed(1)}千元`
|
||||
return `${(value / 10000).toFixed(1)}万元`
|
||||
},
|
||||
|
||||
getWarningClass(value) {
|
||||
if (value === 0) return 'text-green-600'
|
||||
if (value < 3) return 'text-yellow-600'
|
||||
return 'text-red-600'
|
||||
},
|
||||
|
||||
getIndexClass(index) {
|
||||
if (index < 0.3) return 'text-green-600'
|
||||
if (index < 0.7) return 'text-yellow-600'
|
||||
return 'text-red-600'
|
||||
},
|
||||
|
||||
getRecoveryPercentage(recovery) {
|
||||
// 假设最大恢复次数为10次
|
||||
return Math.min((recovery / 10) * 100, 100)
|
||||
},
|
||||
|
||||
getRatioClass(ratio) {
|
||||
if (ratio >= 0.8) return 'text-green-600'
|
||||
if (ratio >= 0.6) return 'text-yellow-600'
|
||||
return 'text-red-600'
|
||||
},
|
||||
|
||||
getRatioBarClass(ratio) {
|
||||
if (ratio >= 0.8) return 'bg-green-500'
|
||||
if (ratio >= 0.6) return 'bg-yellow-500'
|
||||
return 'bg-red-500'
|
||||
},
|
||||
|
||||
getFailureClass(count) {
|
||||
if (count === 0) return 'text-green-600'
|
||||
if (count < 3) return 'text-yellow-600'
|
||||
return 'text-red-600'
|
||||
},
|
||||
|
||||
getFailureBarClass(count) {
|
||||
if (count === 0) return 'bg-green-500'
|
||||
if (count < 3) return 'bg-yellow-500'
|
||||
return 'bg-red-500'
|
||||
},
|
||||
|
||||
getCircleStyle(ratio) {
|
||||
let color = '#ef4444'
|
||||
if (ratio < 0.3) color = '#10b981'
|
||||
else if (ratio < 0.6) color = '#f59e0b'
|
||||
|
||||
// 确保至少显示10度,让用户知道是图表
|
||||
const minDegree = 10
|
||||
const actualDegree = Math.max(ratio * 360, minDegree)
|
||||
|
||||
return {
|
||||
background: `conic-gradient(${color} ${actualDegree}deg, #e5e7eb 0deg)`
|
||||
}
|
||||
},
|
||||
|
||||
getRatioCardClass(id) {
|
||||
// 失败相关的指标使用红色,总体还款成功率使用绿色,其他使用蓝色
|
||||
if (id === 'overall_success_ratio') {
|
||||
return 'bg-[#ECF9EF] border border-[#CAECD3]'
|
||||
} else if (id.includes('failure')) {
|
||||
return 'bg-[#F9ECEC] border border-[#F0CACA]'
|
||||
}
|
||||
return 'bg-[#ECF2FD] border border-[#CADAF9]'
|
||||
},
|
||||
|
||||
getRatioBarBgClass(id) {
|
||||
// 失败相关的指标使用红色背景,总体还款成功率使用绿色背景,其他使用蓝色背景
|
||||
if (id === 'overall_success_ratio') {
|
||||
return 'bg-[#CAECD3]'
|
||||
} else if (id.includes('failure')) {
|
||||
return 'bg-[#F0CACA]'
|
||||
}
|
||||
return 'bg-[#DBE6FC]'
|
||||
},
|
||||
|
||||
getRatioBarColor(id) {
|
||||
// 失败相关的指标使用红色,总体还款成功率使用绿色,其他使用蓝色
|
||||
if (id === 'overall_success_ratio') {
|
||||
return '#5EBC62'
|
||||
} else if (id.includes('failure')) {
|
||||
return '#D44643'
|
||||
}
|
||||
return '#5079EA'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 组件样式已使用 Tailwind CSS */
|
||||
</style>
|
||||
375
src/ui/CJRZQ5E9F/components/RiskOverview.vue
Normal file
375
src/ui/CJRZQ5E9F/components/RiskOverview.vue
Normal file
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="rounded-lg border border-[#99999933] pb-2 mb-4">
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<div class="flex items-center mb-4 p-4">
|
||||
<div class="w-8 h-8 flex items-center justify-center mr-2">
|
||||
<img src="@/assets/images/report/fxgl.png" alt="风险概览" class="w-8 h-8 object-contain" />
|
||||
</div>
|
||||
<span class="font-bold text-gray-800">风险概览</span>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pb-4">
|
||||
<!-- 综合风险等级 -->
|
||||
<div class="mb-6">
|
||||
<div class="p-4 rounded-lg" :class="getRiskCardClass()">
|
||||
<div class="flex items-start">
|
||||
<div class="mr-3 mt-1">
|
||||
<img :src="getRiskIconPath()" alt="综合风险等级" class="w-10 h-10 object-contain" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="font-semibold text-gray-800">综合风险等级</h4>
|
||||
<div class="risk-level-badge" :class="getRiskLevelClass()">
|
||||
{{ riskLevel }}
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm mb-2">
|
||||
基于多维度数据分析的风险评估
|
||||
</p>
|
||||
<p class=" text-sm" :class="riskLevelTextClass">
|
||||
{{ riskDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前状态 -->
|
||||
<div class="mb-6">
|
||||
<LTitle title="当前状态" class="mb-4" />
|
||||
<div class="space-y-4">
|
||||
<!-- 逾期状态 -->
|
||||
<div class="p-4 rounded-lg" :class="getOverdueCardClass()">
|
||||
<div class="flex items-start">
|
||||
<div class="mr-3 mt-1">
|
||||
<img :src="getOverdueIconPath()" alt="逾期状态" class="w-10 h-10 object-contain" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="font-semibold text-gray-800">逾期状态</h4>
|
||||
<div class="risk-level-badge" :class="getOverdueStatusClass()">
|
||||
{{ overdueStatus }}
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">
|
||||
当前逾期情况
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 当前逾期机构 -->
|
||||
<div class="p-4 rounded-lg" :class="getOverdueInstitutionCardClass()">
|
||||
<div class="flex items-start">
|
||||
<div class="mr-3 mt-1">
|
||||
<img :src="getOverdueInstitutionIconPath()" alt="当前逾期机构" class="w-10 h-10 object-contain" />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<h4 class="font-semibold text-gray-800">当前逾期机构</h4>
|
||||
<div class="risk-level-badge" :class="getOverdueInstitutionClass()">
|
||||
{{ currentOverdueInstitutions }}家
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm">
|
||||
逾期机构数量
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关键指标 -->
|
||||
<div class="">
|
||||
<LTitle title="关键指标" />
|
||||
<div class="space-y-2 p-4">
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-[#999999]">贷款总机构数</span>
|
||||
<span class="text-[#333333] font-bold">{{ formatMetricValue(totalInstitutions) }}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-[#999999]">已结清机构数</span>
|
||||
<span class="text-[#333333] font-bold">{{ formatMetricValue(settledInstitutions) }}家</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-[#999999]">信用贷款时长</span>
|
||||
<span class="text-[#333333] font-bold">{{ formatDays(creditLoanDuration) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-[#999999]">最近一次交易距今</span>
|
||||
<span class="text-[#333333] font-bold">{{ formatDays(lastTransactionDays) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center text-sm">
|
||||
<span class="text-[#999999]">最近一次还款距今</span>
|
||||
<span class="text-[#333333] font-bold">{{ formatDays(lastRepaymentDays) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 温馨提示 -->
|
||||
<LRemark
|
||||
content="风险概览提供申请人的整体风险状况总结,包括综合风险等级、当前状态和关键指标。风险等级基于多维度数据分析计算得出,包括但不限于逾期情况、借贷历史、还款表现等。当前状态展示申请人的实时风险指标,包括逾期状态、最近交易情况等。建议结合具体业务场景和风险政策进行综合判断。" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LTitle from '@/components/LTitle.vue'
|
||||
import LRemark from '@/components/LRemark.vue'
|
||||
|
||||
export default {
|
||||
name: 'RiskOverview',
|
||||
components: {
|
||||
LTitle,
|
||||
LRemark
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 解析区间化数据的辅助方法
|
||||
totalInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0001)
|
||||
},
|
||||
settledInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0002)
|
||||
},
|
||||
currentOverdueStatus() {
|
||||
return this.data.xyp_cpl0044 === '1'
|
||||
},
|
||||
currentOverdueInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0071)
|
||||
},
|
||||
creditLoanDuration() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0045)
|
||||
},
|
||||
recent1DayInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0070)
|
||||
},
|
||||
recent7DayInstitutions() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0009)
|
||||
},
|
||||
lastTransactionDays() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0046)
|
||||
},
|
||||
lastRepaymentDays() {
|
||||
return this.parseIntervalValue(this.data.xyp_cpl0068)
|
||||
},
|
||||
|
||||
// 综合风险等级计算
|
||||
riskLevel() {
|
||||
const creditScore = parseFloat(this.data.xyp_cpl0081) || 0
|
||||
const overdueIndex = parseFloat(this.data.xyp_cpl0082) || 0
|
||||
|
||||
if (creditScore > 0.7 || overdueIndex > 0.7 || this.currentOverdueStatus) {
|
||||
return '高风险'
|
||||
} else if (creditScore > 0.4 || overdueIndex > 0.4) {
|
||||
return '中风险'
|
||||
} else {
|
||||
return '低风险'
|
||||
}
|
||||
},
|
||||
|
||||
riskLevelClass() {
|
||||
switch (this.riskLevel) {
|
||||
case '高风险': return 'risk-high'
|
||||
case '中风险': return 'risk-medium'
|
||||
default: return 'risk-low'
|
||||
}
|
||||
},
|
||||
|
||||
riskLevelIconClass() {
|
||||
switch (this.riskLevel) {
|
||||
case '高风险': return 'bg-red-500'
|
||||
case '中风险': return 'bg-yellow-500'
|
||||
default: return 'bg-green-500'
|
||||
}
|
||||
},
|
||||
|
||||
riskLevelTextClass() {
|
||||
switch (this.riskLevel) {
|
||||
case '高风险': return 'text-red-600'
|
||||
case '中风险': return 'text-yellow-600'
|
||||
default: return 'text-green-600'
|
||||
}
|
||||
},
|
||||
|
||||
overdueStatusIconClass() {
|
||||
return this.hasOverdue ? 'bg-red-500' : 'bg-green-500'
|
||||
},
|
||||
|
||||
overdueStatusTextClass() {
|
||||
return this.hasOverdue ? 'text-red-600' : 'text-green-600'
|
||||
},
|
||||
|
||||
riskDescription() {
|
||||
switch (this.riskLevel) {
|
||||
case '高风险': return '存在较高信用风险,建议谨慎放贷'
|
||||
case '中风险': return '信用风险适中,需要进一步评估'
|
||||
default: return '信用风险较低,具备良好还款能力'
|
||||
}
|
||||
},
|
||||
|
||||
overdueStatus() {
|
||||
return this.currentOverdueStatus ? '存在逾期' : '无逾期'
|
||||
},
|
||||
|
||||
overdueStatusClass() {
|
||||
return this.currentOverdueStatus ? 'status-danger' : 'status-success'
|
||||
},
|
||||
|
||||
hasRecentActivity() {
|
||||
return this.recent1DayInstitutions > 0 || this.recent7DayInstitutions > 0
|
||||
},
|
||||
|
||||
hasOverdue() {
|
||||
return this.currentOverdueStatus
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 获取综合风险等级卡片样式
|
||||
getRiskCardClass() {
|
||||
switch (this.riskLevel) {
|
||||
case '高风险':
|
||||
return 'bg-red-50 border border-red-200'
|
||||
case '中风险':
|
||||
return 'bg-yellow-50 border border-yellow-200'
|
||||
default:
|
||||
return 'bg-green-50 border border-green-200'
|
||||
}
|
||||
},
|
||||
|
||||
// 获取综合风险等级图标路径
|
||||
getRiskIconPath() {
|
||||
switch (this.riskLevel) {
|
||||
case '高风险':
|
||||
return new URL('@/assets/images/report/gfx.png', import.meta.url).href
|
||||
case '中风险':
|
||||
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
|
||||
default:
|
||||
return new URL('@/assets/images/report/zq.png', import.meta.url).href
|
||||
}
|
||||
},
|
||||
|
||||
// 获取综合风险等级标签样式
|
||||
getRiskLevelClass() {
|
||||
switch (this.riskLevel) {
|
||||
case '高风险':
|
||||
return 'bg-red-600 text-white'
|
||||
case '中风险':
|
||||
return 'bg-yellow-600 text-white'
|
||||
default:
|
||||
return 'bg-green-600 text-white'
|
||||
}
|
||||
},
|
||||
|
||||
// 获取逾期状态卡片样式
|
||||
getOverdueCardClass() {
|
||||
return this.hasOverdue
|
||||
? 'bg-red-50 border border-red-200'
|
||||
: 'bg-green-50 border border-green-200'
|
||||
},
|
||||
|
||||
// 获取逾期状态图标路径
|
||||
getOverdueIconPath() {
|
||||
return this.hasOverdue
|
||||
? new URL('@/assets/images/report/gfx.png', import.meta.url).href
|
||||
: new URL('@/assets/images/report/zq.png', import.meta.url).href
|
||||
},
|
||||
|
||||
// 获取逾期状态标签样式
|
||||
getOverdueStatusClass() {
|
||||
return this.hasOverdue
|
||||
? 'bg-red-600 text-white'
|
||||
: 'bg-green-600 text-white'
|
||||
},
|
||||
|
||||
// 获取当前逾期机构卡片样式
|
||||
getOverdueInstitutionCardClass() {
|
||||
return this.currentOverdueInstitutions > 0
|
||||
? 'bg-red-50 border border-red-200'
|
||||
: 'bg-green-50 border border-green-200'
|
||||
},
|
||||
|
||||
// 获取当前逾期机构图标路径
|
||||
getOverdueInstitutionIconPath() {
|
||||
return this.currentOverdueInstitutions > 0
|
||||
? new URL('@/assets/images/report/gfx.png', import.meta.url).href
|
||||
: new URL('@/assets/images/report/zq.png', import.meta.url).href
|
||||
},
|
||||
|
||||
// 获取当前逾期机构标签样式
|
||||
getOverdueInstitutionClass() {
|
||||
return this.currentOverdueInstitutions > 0
|
||||
? 'bg-red-600 text-white'
|
||||
: 'bg-green-600 text-white'
|
||||
},
|
||||
// 解析区间化数值
|
||||
parseIntervalValue(value) {
|
||||
if (!value || value === '' || value === '-1') return 0
|
||||
const num = parseInt(value)
|
||||
if (isNaN(num)) return 0
|
||||
|
||||
// 根据区间映射返回大致范围的中值
|
||||
switch (num) {
|
||||
case 1: return 1
|
||||
case 2: return 3
|
||||
case 3: return 7
|
||||
case 4: return 15
|
||||
case 5: return 25
|
||||
default: return num
|
||||
}
|
||||
},
|
||||
|
||||
formatMetricValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value < 5) return `${value}`
|
||||
if (value < 10) return `${value}`
|
||||
return `${value}+`
|
||||
},
|
||||
|
||||
formatDays(value) {
|
||||
if (value === 0) return '无记录'
|
||||
if (value < 30) return `${value}天`
|
||||
if (value < 365) return `${Math.floor(value / 30)}个月`
|
||||
return `${Math.floor(value / 365)}年`
|
||||
},
|
||||
|
||||
getMetricClass(value) {
|
||||
if (value > 10) return 'text-red-600'
|
||||
if (value > 5) return 'text-orange-600'
|
||||
return 'text-green-600'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 风险等级标签 */
|
||||
.risk-level-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 0 8px 0 8px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 卡片需要相对定位 */
|
||||
.p-4.rounded-lg {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
1466
src/ui/CJRZQ5E9F/components/TimeTrendAnalysis.vue
Normal file
1466
src/ui/CJRZQ5E9F/components/TimeTrendAnalysis.vue
Normal file
File diff suppressed because it is too large
Load Diff
122
src/ui/CJRZQ5E9F/index.vue
Normal file
122
src/ui/CJRZQ5E9F/index.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="card">
|
||||
<!-- 风险概览 -->
|
||||
<RiskOverview :data="riskData" />
|
||||
|
||||
<!-- 信用评分 -->
|
||||
<CreditScores :data="riskData" />
|
||||
|
||||
<!-- 贷款行为分析 -->
|
||||
<LoanBehaviorAnalysis :data="riskData" />
|
||||
|
||||
<!-- 机构分析 -->
|
||||
<InstitutionAnalysis :data="riskData" />
|
||||
|
||||
<!-- 时间趋势分析 -->
|
||||
<TimeTrendAnalysis :data="riskData" />
|
||||
|
||||
<!-- 风险指标详情 -->
|
||||
<RiskIndicators :data="riskData" />
|
||||
|
||||
<!-- 专业建议 -->
|
||||
<RiskAdvice :data="riskData" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RiskOverview from './components/RiskOverview.vue'
|
||||
import CreditScores from './components/CreditScores.vue'
|
||||
import LoanBehaviorAnalysis from './components/LoanBehaviorAnalysis.vue'
|
||||
import InstitutionAnalysis from './components/InstitutionAnalysis.vue'
|
||||
import TimeTrendAnalysis from './components/TimeTrendAnalysis.vue'
|
||||
import RiskIndicators from './components/RiskIndicators.vue'
|
||||
import RiskAdvice from './components/RiskAdvice.vue'
|
||||
|
||||
export default {
|
||||
name: 'LoanRiskReport',
|
||||
components: {
|
||||
RiskOverview,
|
||||
CreditScores,
|
||||
LoanBehaviorAnalysis,
|
||||
InstitutionAnalysis,
|
||||
TimeTrendAnalysis,
|
||||
RiskIndicators,
|
||||
RiskAdvice
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
apiId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
notifyRiskStatus: {
|
||||
type: Function,
|
||||
default: () => { },
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
riskData() {
|
||||
return this.data || {}
|
||||
},
|
||||
hasRisk() {
|
||||
return Object.keys(this.data || {}).length > 0;
|
||||
},
|
||||
riskScore() {
|
||||
const d = this.riskData;
|
||||
|
||||
// 检查是否有数据
|
||||
if (!d || Object.keys(d).length === 0) {
|
||||
return 100; // 无数据视为最安全
|
||||
}
|
||||
|
||||
// 根据风险概览数据计算评分
|
||||
// 假设数据中有风险评分字段
|
||||
const riskLevel = d.riskLevel || d.risk_level || '';
|
||||
const riskScore = d.riskScore || d.risk_score || 0;
|
||||
|
||||
// 如果有风险评分,直接使用
|
||||
if (riskScore > 0) {
|
||||
// 风险评分转换为安全评分(分数越高越安全)
|
||||
// 假设风险评分是0-100,分数越高风险越大
|
||||
return Math.max(0, 100 - riskScore);
|
||||
}
|
||||
|
||||
// 根据风险等级计算
|
||||
if (riskLevel === 'high' || riskLevel === 'HIGH' || riskLevel === '高风险') {
|
||||
return 20; // 高风险
|
||||
}
|
||||
if (riskLevel === 'medium' || riskLevel === 'MEDIUM' || riskLevel === '中风险') {
|
||||
return 60; // 中等风险
|
||||
}
|
||||
if (riskLevel === 'low' || riskLevel === 'LOW' || riskLevel === '低风险') {
|
||||
return 80; // 低风险
|
||||
}
|
||||
|
||||
// 默认中等风险
|
||||
return 70;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
riskScore(newValue) {
|
||||
if (this.apiId && this.notifyRiskStatus) {
|
||||
this.notifyRiskStatus(this.apiId, this.index, newValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 立即通知一次
|
||||
if (this.apiId && this.notifyRiskStatus) {
|
||||
this.notifyRiskStatus(this.apiId, this.index, this.riskScore);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
317
src/ui/CJRZQ5E9F/utils/simpleSplitter.js
Normal file
317
src/ui/CJRZQ5E9F/utils/simpleSplitter.js
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* 贷款风险报告(CJRZQ5E9F)数据拆分工具
|
||||
* 将完整的贷款风险报告数据拆分成多个独立的模块,用于在不同的tab中显示
|
||||
*/
|
||||
|
||||
/**
|
||||
* 将CJRZQ5E9F数据拆分为多个独立的tab模块
|
||||
* @param {Array} reportData - 原始报告数据数组
|
||||
* @returns {Array} 拆分后的模块数组
|
||||
*/
|
||||
export function splitCJRZQ5E9FForTabs(reportData) {
|
||||
// 查找CJRZQ5E9F数据
|
||||
const cjrzq5e9fData = reportData.find(
|
||||
(item) => item.data?.apiID === "JRZQ5E9F"
|
||||
);
|
||||
|
||||
if (!cjrzq5e9fData || !cjrzq5e9fData.data?.data) {
|
||||
return reportData; // 如果没有找到CJRZQ5E9F数据,返回原数据
|
||||
}
|
||||
|
||||
const originalData = cjrzq5e9fData.data.data;
|
||||
const baseTimestamp = cjrzq5e9fData.data.timestamp;
|
||||
|
||||
// 创建拆分后的模块数组
|
||||
const splitModules = [];
|
||||
|
||||
// 1. 风险概览
|
||||
if (originalData && Object.keys(originalData).length > 0) {
|
||||
splitModules.push({
|
||||
data: {
|
||||
apiID: "CJRZQ5E9F_RiskOverview",
|
||||
data: originalData,
|
||||
success: true,
|
||||
timestamp: baseTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 2. 信用评分
|
||||
if (originalData && Object.keys(originalData).length > 0) {
|
||||
splitModules.push({
|
||||
data: {
|
||||
apiID: "CJRZQ5E9F_CreditScores",
|
||||
data: originalData,
|
||||
success: true,
|
||||
timestamp: baseTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 贷款行为分析
|
||||
if (originalData && Object.keys(originalData).length > 0) {
|
||||
splitModules.push({
|
||||
data: {
|
||||
apiID: "CJRZQ5E9F_LoanBehaviorAnalysis",
|
||||
data: originalData,
|
||||
success: true,
|
||||
timestamp: baseTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 机构分析
|
||||
if (originalData && Object.keys(originalData).length > 0) {
|
||||
splitModules.push({
|
||||
data: {
|
||||
apiID: "CJRZQ5E9F_InstitutionAnalysis",
|
||||
data: originalData,
|
||||
success: true,
|
||||
timestamp: baseTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 5. 时间趋势分析
|
||||
if (originalData && Object.keys(originalData).length > 0) {
|
||||
splitModules.push({
|
||||
data: {
|
||||
apiID: "CJRZQ5E9F_TimeTrendAnalysis",
|
||||
data: originalData,
|
||||
success: true,
|
||||
timestamp: baseTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 6. 风险指标详情
|
||||
if (originalData && Object.keys(originalData).length > 0) {
|
||||
splitModules.push({
|
||||
data: {
|
||||
apiID: "CJRZQ5E9F_RiskIndicators",
|
||||
data: originalData,
|
||||
success: true,
|
||||
timestamp: baseTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 7. 专业建议
|
||||
if (originalData && Object.keys(originalData).length > 0) {
|
||||
splitModules.push({
|
||||
data: {
|
||||
apiID: "CJRZQ5E9F_RiskAdvice",
|
||||
data: originalData,
|
||||
success: true,
|
||||
timestamp: baseTimestamp,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 移除原始的JRZQ5E9F数据,添加拆分后的模块
|
||||
const otherData = reportData.filter(
|
||||
(item) => item.data?.apiID !== "JRZQ5E9F"
|
||||
);
|
||||
|
||||
return [...otherData, ...splitModules];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析区间化数值
|
||||
* @param {string|number} value - 原始值
|
||||
* @returns {number} 解析后的数值
|
||||
*/
|
||||
export function parseIntervalValue(value) {
|
||||
if (!value || value === "" || value === "-1") return 0;
|
||||
const num = parseInt(value);
|
||||
if (isNaN(num)) return 0;
|
||||
|
||||
// 根据区间映射返回大致范围的中值
|
||||
switch (num) {
|
||||
case 1:
|
||||
return 1;
|
||||
case 2:
|
||||
return 3;
|
||||
case 3:
|
||||
return 7;
|
||||
case 4:
|
||||
return 15;
|
||||
case 5:
|
||||
return 25;
|
||||
default:
|
||||
return num;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化指标值显示
|
||||
* @param {number} value - 数值
|
||||
* @returns {string} 格式化后的显示文本
|
||||
*/
|
||||
export function formatMetricValue(value) {
|
||||
if (value === 0) return "0";
|
||||
if (value < 5) return `${value}`;
|
||||
return `${value}+`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化天数显示
|
||||
* @param {number} value - 天数
|
||||
* @returns {string} 格式化后的显示文本
|
||||
*/
|
||||
export function formatDays(value) {
|
||||
if (value === 0) return "无记录";
|
||||
if (value < 30) return `${value}天`;
|
||||
if (value < 365) return `${Math.floor(value / 30)}个月`;
|
||||
return `${Math.floor(value / 365)}年`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化金额显示
|
||||
* @param {number} value - 金额
|
||||
* @returns {string} 格式化后的显示文本
|
||||
*/
|
||||
export function formatAmount(value) {
|
||||
if (value === 0) return "0元";
|
||||
if (value < 1000) return `${value}元`;
|
||||
if (value < 10000) return `${(value / 1000).toFixed(1)}千元`;
|
||||
return `${(value / 10000).toFixed(1)}万元`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算风险等级
|
||||
* @param {number} creditScore - 信用风险评分
|
||||
* @param {number} overdueIndex - 逾期指数
|
||||
* @param {boolean} currentOverdue - 当前是否逾期
|
||||
* @returns {object} 包含等级、颜色和描述的对象
|
||||
*/
|
||||
export function calculateRiskLevel(creditScore, overdueIndex, currentOverdue) {
|
||||
if (creditScore > 0.7 || overdueIndex > 0.7 || currentOverdue) {
|
||||
return {
|
||||
level: "高风险",
|
||||
color: "text-red-600",
|
||||
bgColor: "bg-red-100",
|
||||
iconColor: "bg-red-500",
|
||||
description: "存在较高信用风险,建议谨慎放贷",
|
||||
};
|
||||
} else if (creditScore > 0.4 || overdueIndex > 0.4) {
|
||||
return {
|
||||
level: "中风险",
|
||||
color: "text-yellow-600",
|
||||
bgColor: "bg-yellow-100",
|
||||
iconColor: "bg-yellow-500",
|
||||
description: "信用风险适中,需要进一步评估",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
level: "低风险",
|
||||
color: "text-green-600",
|
||||
bgColor: "bg-green-100",
|
||||
iconColor: "bg-green-500",
|
||||
description: "信用风险较低,具备良好还款能力",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算信用评分显示
|
||||
* @param {number} creditRiskScore - 信用风险评分
|
||||
* @param {number} amountComplianceIndex - 履约金额综合指数
|
||||
* @param {number} countComplianceIndex - 履约笔数综合指数
|
||||
* @returns {object} 包含评分、进度和颜色的对象
|
||||
*/
|
||||
export function calculateCreditScore(
|
||||
creditRiskScore,
|
||||
amountComplianceIndex,
|
||||
countComplianceIndex
|
||||
) {
|
||||
const avgRisk =
|
||||
(creditRiskScore + amountComplianceIndex + countComplianceIndex) / 3;
|
||||
// 风险越高,信用分越低
|
||||
const score = Math.round((1 - avgRisk) * 850 + 150);
|
||||
const progress = (score / 1000) * 283;
|
||||
|
||||
let color = "#ef4444";
|
||||
if (score >= 750) color = "#10b981";
|
||||
else if (score >= 650) color = "#f59e0b";
|
||||
|
||||
return {
|
||||
score,
|
||||
progress,
|
||||
color,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信用等级描述
|
||||
* @param {number} score - 信用评分
|
||||
* @returns {string} 等级描述
|
||||
*/
|
||||
export function getCreditScoreLevel(score) {
|
||||
if (score >= 800) return "优秀";
|
||||
if (score >= 700) return "良好";
|
||||
if (score >= 600) return "一般";
|
||||
if (score >= 500) return "较差";
|
||||
return "很差";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取信用等级样式类
|
||||
* @param {number} score - 信用评分
|
||||
* @returns {string} 样式类名
|
||||
*/
|
||||
export function getCreditScoreBadgeClass(score) {
|
||||
if (score >= 800) return "bg-green-100 text-green-800";
|
||||
if (score >= 700) return "bg-blue-100 text-blue-800";
|
||||
if (score >= 600) return "bg-yellow-100 text-yellow-800";
|
||||
if (score >= 500) return "bg-orange-100 text-orange-800";
|
||||
return "bg-red-100 text-red-800";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评分样式类
|
||||
* @param {number} score - 评分
|
||||
* @returns {string} 样式类名
|
||||
*/
|
||||
export function getScoreClass(score) {
|
||||
if (score === null) return "text-gray-400";
|
||||
if (score >= 750) return "text-green-600";
|
||||
if (score >= 650) return "text-yellow-600";
|
||||
return "text-red-600";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取圆形进度样式
|
||||
* @param {number} ratio - 比例值 (0-1)
|
||||
* @returns {object} 样式对象
|
||||
*/
|
||||
export function getCircleStyle(ratio) {
|
||||
let color = "#ef4444";
|
||||
if (ratio >= 0.8) color = "#10b981";
|
||||
else if (ratio >= 0.6) color = "#f59e0b";
|
||||
|
||||
// 确保至少显示10度,让用户知道是图表
|
||||
const minDegree = 10;
|
||||
const actualDegree = Math.max(ratio * 360, minDegree);
|
||||
|
||||
return {
|
||||
background: `conic-gradient(${color} ${actualDegree}deg, #e5e7eb 0deg)`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有风险数据
|
||||
* @param {Object} data - 数据对象
|
||||
* @returns {boolean} 是否有风险
|
||||
*/
|
||||
export function hasRiskData(data) {
|
||||
if (!data) return false;
|
||||
|
||||
// 检查对象中是否有非0值
|
||||
return Object.values(data).some((value) => {
|
||||
if (typeof value === "number") return value > 0;
|
||||
if (typeof value === "string")
|
||||
return value !== "0" && value !== "-" && value !== "";
|
||||
return false;
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user