version temp2

This commit is contained in:
2025-10-24 17:08:42 +08:00
parent c1dd5551f0
commit 1a919d57ba
193 changed files with 15044 additions and 15687 deletions

View File

@@ -1,95 +1,36 @@
<template>
<div class="time-trend-analysis">
<LTitle title="时间趋势分析" />
<div class="section-spacing"></div>
<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/zwsc.png" alt="贷款行为分析" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">贷款行为分析</span>
</div>
<!-- 交易金额趋势 -->
<div class="mb-6">
<div class="flex items-center mb-4">
<div class="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center mr-2">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 8.707 8.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<h3 class="text-base font-semibold text-gray-800">交易金额趋势</h3>
</div>
<LTitle title="交易金额趋势" class="mb-2" />
<!-- 图表展示 -->
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-lg p-4 border border-blue-200 mb-6">
<div class="text-sm font-medium text-gray-800 mb-4 text-center">交易金额趋势图</div>
<div class="relative h-48 mb-4">
<!-- Y轴标签 -->
<div class="absolute left-0 top-0 h-full flex flex-col justify-between text-xs text-gray-500 pr-2">
<span>{{ formatAmount(maxAmountTrend) }}</span>
<span>{{ formatAmount(Math.round(maxAmountTrend * 0.75)) }}</span>
<span>{{ formatAmount(Math.round(maxAmountTrend * 0.5)) }}</span>
<span>{{ formatAmount(Math.round(maxAmountTrend * 0.25)) }}</span>
<span>0</span>
</div>
<!-- 图表区域 -->
<div class="ml-8 h-full relative border-l border-b border-gray-300">
<!-- 水平网格线 -->
<div class="absolute inset-0">
<div class="absolute top-0 w-full border-t border-gray-200"></div>
<div class="absolute top-1/4 w-full border-t border-gray-200"></div>
<div class="absolute top-2/4 w-full border-t border-gray-200"></div>
<div class="absolute top-3/4 w-full border-t border-gray-200"></div>
</div>
<!-- 柱状图 -->
<div class="absolute bottom-0 w-full h-full flex items-end justify-around px-2">
<div v-for="(point, index) in amountTrendPoints" :key="point.label"
class="flex flex-col items-center w-full max-w-16">
<!-- 金额柱子 -->
<div class="w-8 bg-blue-500 rounded-t transition-all duration-500 hover:bg-blue-600 relative group"
:style="`height: ${Math.max((point.value / (maxAmountTrend || 1)) * 180, 4)}px`">
<!-- 悬停提示 -->
<div
class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
金额: {{ formatAmount(point.value) }}
</div>
</div>
<!-- 时间标签 -->
<div class="text-xs text-gray-600 mt-2 text-center transform -rotate-45 origin-center">
{{ point.label.replace('最近', '近').replace('天', '日') }}
</div>
</div>
</div>
</div>
</div>
<!-- X轴单位标签 -->
<div class="text-center mb-4">
<div class="text-xs text-gray-600 font-medium">时间维度</div>
</div>
<!-- 图例 -->
<div class="flex justify-center items-center space-x-4 text-xs">
<div class="flex items-center">
<div class="w-3 h-3 bg-blue-500 rounded mr-2"></div>
<span class="text-gray-600">交易金额</span>
</div>
</div>
<!-- ECharts 图表 -->
<div class="mb-6">
<div ref="amountChartRef" :style="{ width: '100%', height: '300px' }"></div>
</div>
<!-- 数值统计 -->
<div class="bg-white rounded-lg border border-gray-200 p-4">
<div class="bg-[#ECF2FD] rounded-lg border border-[#CADAF9] p-4 mx-4">
<div class="flex justify-between gap-4">
<div class="flex-1 text-center">
<div class="text-sm font-bold text-blue-600">{{ formatAmount(maxAmountTrend) }}</div>
<div class="text-xs text-gray-600">峰值</div>
<div class="text-[#999999]">峰值</div>
<div class="text-[#2B79EE] font-bold">{{ formatAmount(maxAmountTrend) }}</div>
</div>
<div class="flex-1 text-center">
<div class="text-sm font-bold text-green-600">{{ formatAmount(minAmountTrend) }}</div>
<div class="text-xs text-gray-600">低值</div>
<div class="text-[#999999]">低值</div>
<div class="text-[#2B79EE] font-bold">{{ formatAmount(minAmountTrend) }}</div>
</div>
<div class="flex-1 text-center">
<div class="text-sm font-bold text-purple-600">{{ formatAmount(avgAmountTrend) }}</div>
<div class="text-xs text-gray-600">均值</div>
<div class="text-[#999999]">均值</div>
<div class="text-[#2B79EE] font-bold">{{ formatAmount(avgAmountTrend) }}</div>
</div>
</div>
</div>
@@ -97,90 +38,27 @@
<!-- 交易笔数趋势 -->
<div class="mb-6">
<div class="flex items-center mb-4">
<div class="w-6 h-6 bg-green-500 rounded-full flex items-center justify-center mr-2">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path
d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
</svg>
</div>
<h3 class="text-base font-semibold text-gray-800">交易笔数趋势</h3>
</div>
<LTitle title="交易笔数趋势" class="mb-2" />
<!-- 图表展示 -->
<div class="bg-gradient-to-br from-green-50 to-emerald-50 rounded-lg p-4 border border-green-200 mb-6">
<div class="text-sm font-medium text-gray-800 mb-4 text-center">交易笔数趋势图</div>
<div class="relative h-48 mb-4">
<!-- Y轴标签 -->
<div class="absolute left-0 top-0 h-full flex flex-col justify-between text-xs text-gray-500 pr-2">
<span>{{ maxCountTrend }}</span>
<span>{{ Math.round(maxCountTrend * 0.75) }}</span>
<span>{{ Math.round(maxCountTrend * 0.5) }}</span>
<span>{{ Math.round(maxCountTrend * 0.25) }}</span>
<span>0</span>
</div>
<!-- 图表区域 -->
<div class="ml-8 h-full relative border-l border-b border-gray-300">
<!-- 水平网格线 -->
<div class="absolute inset-0">
<div class="absolute top-0 w-full border-t border-gray-200"></div>
<div class="absolute top-1/4 w-full border-t border-gray-200"></div>
<div class="absolute top-2/4 w-full border-t border-gray-200"></div>
<div class="absolute top-3/4 w-full border-t border-gray-200"></div>
</div>
<!-- 柱状图 -->
<div class="absolute bottom-0 w-full h-full flex items-end justify-around px-2">
<div v-for="(item, index) in countTrendData" :key="item.label"
class="flex flex-col items-center w-full max-w-16">
<!-- 笔数柱子 -->
<div class="w-8 bg-green-500 rounded-t transition-all duration-500 hover:bg-green-600 relative group"
:style="`height: ${Math.max((item.value / (maxCountTrend || 1)) * 180, 4)}px`">
<!-- 悬停提示 -->
<div
class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
笔数: {{ item.value }}
</div>
</div>
<!-- 时间标签 -->
<div class="text-xs text-gray-600 mt-2 text-center transform -rotate-45 origin-center">
{{ item.label }}
</div>
</div>
</div>
</div>
</div>
<!-- X轴单位标签 -->
<div class="text-center mb-4">
<div class="text-xs text-gray-600 font-medium">时间维度</div>
</div>
<!-- 图例 -->
<div class="flex justify-center items-center space-x-4 text-xs">
<div class="flex items-center">
<div class="w-3 h-3 bg-green-500 rounded mr-2"></div>
<span class="text-gray-600">交易笔数</span>
</div>
</div>
<!-- ECharts 图表 -->
<div class="mb-6">
<div ref="countChartRef" :style="{ width: '100%', height: '300px' }"></div>
</div>
<!-- 统计摘要 -->
<div class="bg-white rounded-lg border border-gray-200 p-4">
<div class="bg-[#ECF2FD] rounded-lg border border-[#CADAF9] p-4 mx-4">
<div class="flex justify-between gap-4">
<div class="flex-1 text-center">
<div class="text-sm font-bold text-green-600">{{ maxCountTrend }}</div>
<div class="text-xs text-gray-600">峰值</div>
<div class="text-[#999999]">峰值</div>
<div class="text-[#2B79EE] font-bold">{{ maxCountTrend }} </div>
</div>
<div class="flex-1 text-center">
<div class="text-sm font-bold text-blue-600">{{ minCountTrend }}</div>
<div class="text-xs text-gray-600">低值</div>
<div class="text-[#999999]">低值</div>
<div class="text-[#2B79EE] font-bold">{{ minCountTrend }} </div>
</div>
<div class="flex-1 text-center">
<div class="text-sm font-bold text-purple-600">{{ avgCountTrend.toFixed(0) }}</div>
<div class="text-xs text-gray-600">均值</div>
<div class="text-[#999999]">均值</div>
<div class="text-[#2B79EE] font-bold">{{ avgCountTrend.toFixed(0) }} </div>
</div>
</div>
</div>
@@ -188,34 +66,24 @@
<!-- 还款成功率趋势 -->
<div class="mb-6">
<div class="flex items-center mb-4">
<div class="w-6 h-6 bg-orange-500 rounded-full flex items-center justify-center mr-2">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
</div>
<h3 class="text-base font-semibold text-gray-800">还款成功率趋势</h3>
</div>
<LTitle title="还款成功率趋势" class="mb-2" />
<div class="bg-white rounded-lg border border-gray-200 p-4">
<div class="space-y-4">
<div class="bg-gray-50 rounded-lg p-3" v-for="rate in successRateTrend" :key="rate.period">
<div class="flex justify-between items-center mb-2">
<span class="text-sm font-semibold text-gray-800">{{ rate.period }}</span>
<span class="font-bold" :class="getRateClass(rate.rate)">
{{ (rate.rate * 100).toFixed(1) }}%
</span>
</div>
<div class="h-2 bg-gray-200 rounded-full overflow-hidden mb-2">
<div class="h-full rounded-full transition-all duration-300"
:style="`width: ${Math.max(rate.rate * 100, 2)}%`" :class="getRateBarClass(rate.rate)"></div>
</div>
<div class="flex justify-between text-xs text-gray-600">
<span>成功: {{ rate.success }}</span>
<span>失败: {{ rate.failure }}</span>
</div>
<div class="space-y-3 px-4">
<div class="bg-[#ECF2FD] rounded-lg border border-[#CADAF9] p-4" v-for="rate in successRateTrend"
:key="rate.period">
<div class="flex justify-between items-center mb-3">
<span class="text-base font-bold text-[#333333]">{{ rate.period }}</span>
<span class="text-base font-bold text-[#333333]">
{{ (rate.rate * 100).toFixed(1) }}%
</span>
</div>
<div class="h-2 bg-[#DBE6FC] rounded-full overflow-hidden mb-3">
<div class="h-full rounded-full transition-all duration-300"
:style="`width: ${Math.max(rate.rate * 100, 2)}%; background-color: #5079EA;`"></div>
</div>
<div class="flex justify-between text-xs text-[#999999]">
<span>成功: {{ rate.success }}</span>
<span>失败: {{ rate.failure }}</span>
</div>
</div>
</div>
@@ -223,55 +91,36 @@
<!-- 机构数量变化趋势 -->
<div class="mb-6">
<div class="flex items-center mb-4">
<div class="w-6 h-6 bg-purple-500 rounded-full flex items-center justify-center mr-2">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a1 1 0 110 2h-3a1 1 0 01-1-1v-6a1 1 0 00-1-1H9a1 1 0 00-1 1v6a1 1 0 01-1 1H4a1 1 0 110-2V4zm3 1h2v2H7V5zm2 4H7v2h2V9zm2-4h2v2h-2V5zm2 4h-2v2h2V9z"
clip-rule="evenodd" />
</svg>
</div>
<h3 class="text-base font-semibold text-gray-800">机构数量变化</h3>
</div>
<LTitle title="机构数量变化" class="mb-2" />
<div class="bg-white rounded-lg border border-gray-200 p-4">
<div class="space-y-4">
<div class="bg-gray-50 rounded-lg p-3" v-for="item in institutionTrendData" :key="item.period">
<div class="flex justify-between items-center mb-3">
<span class="text-sm font-semibold text-gray-800">{{ item.period }}</span>
<div class="flex items-center gap-3">
<span class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs font-medium">总数: {{ item.total
}}</span>
<span class="px-2 py-1 bg-green-100 text-green-800 rounded text-xs font-medium" v-if="item.new > 0">新增:
{{ item.new }}</span>
</div>
</div>
<div class="space-y-3 px-4">
<div class="bg-[#ECF2FD] rounded-lg border border-[#CADAF9] p-4" v-for="item in institutionTrendData"
:key="item.period">
<div class="flex justify-between items-center mb-3">
<span class="text-base font-bold text-[#333333]">{{ item.period }}</span>
<span class="text-base font-bold text-[#333333]">总数: {{ item.total }}</span>
</div>
<div class="mb-3">
<div class="h-2 bg-gray-200 rounded-full overflow-hidden flex">
<div class="bg-blue-500 transition-all duration-300"
:style="`width: ${Math.max(item.totalPercentage, 2)}%`"></div>
<div class="bg-green-500 transition-all duration-300"
:style="`width: ${Math.max(item.newPercentage, 1)}%`" v-if="item.new > 0"></div>
</div>
</div>
<div class="h-2 bg-[#DBE6FC] rounded-full overflow-hidden mb-3">
<div class="h-full rounded-full transition-all duration-300"
:style="`width: ${Math.max(item.totalPercentage, 2)}%; background-color: #5079EA;`"></div>
</div>
<div class="flex items-center gap-2" :class="item.trendClass">
<svg v-if="item.trendText === '增长趋势'" class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
</svg>
<svg v-else-if="item.trendText === '下降趋势'" class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
<svg v-else class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd" />
</svg>
<span class="text-xs font-medium">{{ item.trendText }}</span>
</div>
<div class="flex items-center gap-2 text-xs text-[#999999]">
<svg v-if="item.trendText === '增长趋势'" class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
</svg>
<svg v-else-if="item.trendText === '下降趋势'" class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
<svg v-else class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd" />
</svg>
<span>{{ item.trendText }}</span>
</div>
</div>
</div>
@@ -279,46 +128,58 @@
<!-- 新增机构金额分析 -->
<div class="mb-6">
<div class="flex items-center mb-4">
<div class="w-6 h-6 bg-indigo-500 rounded-full flex items-center justify-center mr-2">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path
d="M8.433 7.418c.155-.103.346-.196.567-.267v1.698a2.305 2.305 0 01-.567-.267C8.07 8.34 8 8.114 8 8c0-.114.07-.34.433-.582zM11 12.849v-1.698c.22.071.412.164.567.267.364.243.433.468.433.582 0 .114-.07.34-.433.582a2.305 2.305 0 01-.567.267z" />
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z"
clip-rule="evenodd" />
</svg>
</div>
<h3 class="text-base font-semibold text-gray-800">新增机构金额分析</h3>
</div>
<LTitle title="新增机构金额分析" class="mb-2" />
<div class="bg-white rounded-lg border border-gray-200 p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-gray-50 rounded-lg p-3" v-for="item in newInstitutionAmounts" :key="item.period">
<div class="flex justify-between items-center mb-3">
<span class="text-sm font-semibold text-gray-800">{{ item.period }}</span>
<span class="text-sm font-bold text-indigo-600">{{ formatAmount(item.totalAmount) }}</span>
</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="activeAmountPeriod" line-width="20" line-height="2" color="#a22525"
class="loan-evaluation-tabs">
<van-tab v-for="item in newInstitutionAmounts" :key="item.period" :name="item.period"
:title="item.period" />
</van-tabs>
</div>
<div class="space-y-2 mb-3">
<div class="flex justify-between text-xs">
<span class="text-gray-600">最大单笔</span>
<span class="font-medium text-gray-800">{{ formatAmount(item.maxAmount) }}</span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-600">最小单笔</span>
<span class="font-medium text-gray-800">{{ formatAmount(item.minAmount) }}</span>
</div>
<div class="flex justify-between text-xs">
<span class="text-gray-600">平均金额</span>
<span class="font-medium text-gray-800">{{ formatAmount(item.avgAmount) }}</span>
</div>
</div>
<!-- 内容显示 -->
<div class="loan-evaluation-content">
<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">{{ formatAmount(currentAmountData.totalAmount)
}}</span>
</div>
<div class="h-2 rounded-full bg-gray-200">
<div class="h-2 rounded-full transition-all duration-500"
:style="`width: ${Math.max(currentAmountData.percentage, 2)}%; background-color: #10b981;`">
</div>
</div>
</div>
<div class="h-1.5 bg-gray-200 rounded-full overflow-hidden">
<div
class="h-full bg-gradient-to-r from-indigo-500 to-indigo-600 rounded-full transition-all duration-300"
:style="`width: ${Math.max(item.percentage, 2)}%`"></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">{{ formatAmount(currentAmountData.maxAmount) }}</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">{{ formatAmount(currentAmountData.minAmount) }}</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">{{ formatAmount(currentAmountData.avgAmount) }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -327,85 +188,58 @@
<!-- 风险指标时间分布 -->
<div class="mb-6">
<div class="flex items-center mb-4">
<div class="w-6 h-6 bg-red-500 rounded-full flex items-center justify-center mr-2">
<svg class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"
clip-rule="evenodd" />
</svg>
</div>
<h3 class="text-base font-semibold text-gray-800">风险时间分布</h3>
</div>
<LTitle title="风险指标时间分布" class="mb-2" />
<div class="bg-white rounded-lg border border-gray-200 p-4">
<div class="px-4">
<!-- 风险事件列表 -->
<div class="space-y-3 mb-4" v-if="riskTimeline.length > 0">
<div class="flex items-center gap-3 p-2 rounded-lg" v-for="event in riskTimeline" :key="event.id" :class="event.riskLevel === 'high-risk' ? 'bg-red-50 border border-red-200' :
event.riskLevel === 'medium-risk' ? 'bg-yellow-50 border border-yellow-200' :
'bg-green-50 border border-green-200'">
<div class="w-6 h-6 rounded-full flex items-center justify-center" :class="event.riskLevel === 'high-risk' ? 'bg-red-500' :
event.riskLevel === 'medium-risk' ? 'bg-yellow-500' :
'bg-green-500'">
<svg v-if="event.riskLevel === 'high-risk'" class="w-3 h-3 text-white" fill="currentColor"
viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clip-rule="evenodd" />
</svg>
<svg v-else-if="event.riskLevel === 'medium-risk'" class="w-3 h-3 text-white" fill="currentColor"
viewBox="0 0 20 20">
<path d="M10 12a2 2 0 100-4 2 2 0 000 4z" />
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.332 8.027a6.012 6.012 0 011.912-2.706C6.512 5.73 6.974 6 7.5 6A1.5 1.5 0 019 7.5V8a2 2 0 004 0v-.5A1.5 1.5 0 0114.5 6c.526 0 .988-.27 1.256-.679a6.012 6.012 0 011.912 2.706A8.03 8.03 0 0118 10a8.03 8.03 0 01-.332 2.027 6.012 6.012 0 01-1.912 2.706c-.268.41-.73.679-1.256.679A1.5 1.5 0 0113 13.5V13a2 2 0 10-4 0v.5A1.5 1.5 0 017.5 14c-.526 0-.988.27-1.256.679a6.012 6.012 0 01-1.912-2.706A8.03 8.03 0 014 10a8.03 8.03 0 01.332-2.027z"
clip-rule="evenodd" />
</svg>
<svg v-else class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd" />
</svg>
<div class="flex items-center gap-3 p-3 rounded-lg" v-for="event in riskTimeline" :key="event.id"
:class="getRiskEventCardClass(event.riskLevel)">
<div class="w-10 h-10 flex-shrink-0">
<img :src="getRiskEventIcon(event.riskLevel)" :alt="event.title" class="w-10 h-10 object-contain" />
</div>
<div class="flex-1">
<div class="flex justify-between items-start">
<div>
<h4 class="text-sm font-semibold text-gray-800">{{ event.title }}</h4>
<p class="text-xs text-gray-600">{{ event.description }}</p>
<h4 class="text-sm font-bold text-[#333333]">{{ event.title }}</h4>
<p class="text-xs text-[#999999] mt-1">{{ event.description }}</p>
</div>
<span class="text-xs text-gray-500">{{ event.timeAgo }}</span>
<span class="text-xs text-[#999999] whitespace-nowrap ml-2">{{ event.timeAgo }}</span>
</div>
</div>
</div>
</div>
<!-- 统计摘要 -->
<div class="bg-gray-50 rounded-lg p-3 border-t border-gray-200">
<div class="bg-[#F9F9F9] rounded-lg border p-4 border-[#EEEEEE]">
<div class="flex justify-between text-center">
<div class="flex-1">
<div class="text-lg font-bold text-red-600">{{ riskEventCounts.high }}</div>
<div class="text-xs text-gray-600">高风险事件</div>
<div class="text-lg font-bold text-red-600">{{ riskEventCounts.high }}</div>
</div>
<div class="flex-1">
<div class="text-lg font-bold text-yellow-600">{{ riskEventCounts.medium }}</div>
<div class="text-xs text-gray-600">中风险事件</div>
<div class="text-lg font-bold text-yellow-600">{{ riskEventCounts.medium }}</div>
</div>
<div class="flex-1">
<div class="text-lg font-bold text-green-600">{{ riskEventCounts.low }}</div>
<div class="text-xs text-gray-600">低风险事件</div>
<div class="text-lg font-bold text-green-600">{{ riskEventCounts.low }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 温馨提示 -->
<LRemark content="时间趋势分析通过可视化图表展示申请人的交易金额趋势、申请频率趋势和风险变化趋势。图表数据基于历史申请记录生成可以直观反映申请行为的时间规律性。建议重点关注异常波动时期如短期内交易金额大幅增长或申请频率异常增高的情况。趋势分析有助于识别周期性风险模式和预测未来风险走向。数据统计周期通常为近12个月。" />
<LRemark
content="时间趋势分析通过可视化图表展示申请人的交易金额趋势、申请频率趋势和风险变化趋势。图表数据基于历史申请记录生成可以直观反映申请行为的时间规律性。建议重点关注异常波动时期如短期内交易金额大幅增长或申请频率异常增高的情况。趋势分析有助于识别周期性风险模式和预测未来风险走向。数据统计周期通常为近12个月。" />
</template>
<script>
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import * as echarts from 'echarts'
export default {
name: 'TimeTrendAnalysis',
@@ -419,6 +253,13 @@ export default {
default: () => ({})
}
},
data() {
return {
amountChartInstance: null,
countChartInstance: null,
activeAmountPeriod: '最近90天新增'
}
},
computed: {
// 交易金额趋势点
amountTrendPoints() {
@@ -613,21 +454,21 @@ export default {
newInstitutionAmounts() {
const amounts = [
{
period: '最近90天新增',
period: '最近90天',
totalAmount: this.parseIntervalValue(this.data.xyp_t01aczfzz),
maxAmount: this.parseIntervalValue(this.data.xyp_t01aazfzz),
minAmount: this.parseIntervalValue(this.data.xyp_t01abzfzz),
avgAmount: this.parseIntervalValue(this.data.xyp_t01adzfbz)
},
{
period: '最近180天新增',
period: '最近180天',
totalAmount: this.parseIntervalValue(this.data.xyp_t01aczgzz),
maxAmount: this.parseIntervalValue(this.data.xyp_t01aazgzc),
minAmount: this.parseIntervalValue(this.data.xyp_t01abzgzc),
avgAmount: this.parseIntervalValue(this.data.xyp_t01adzgzc)
},
{
period: '最近360天新增',
period: '最近360天',
totalAmount: this.parseIntervalValue(this.data.xyp_t01achzzc),
maxAmount: this.parseIntervalValue(this.data.xyp_t01aazhzz),
minAmount: 0, // 没有对应的最小值字段
@@ -650,10 +491,227 @@ export default {
else counts.low++
})
return counts
},
// 当前选中的金额数据
currentAmountData() {
return this.newInstitutionAmounts.find(item => item.period === this.activeAmountPeriod) || this.newInstitutionAmounts[0]
}
},
mounted() {
this.initAmountChart()
this.initCountChart()
window.addEventListener('resize', this.handleResize)
},
beforeUnmount() {
if (this.amountChartInstance) {
this.amountChartInstance.dispose()
this.amountChartInstance = null
}
if (this.countChartInstance) {
this.countChartInstance.dispose()
this.countChartInstance = null
}
window.removeEventListener('resize', this.handleResize)
},
watch: {
amountTrendPoints() {
this.updateAmountChart()
},
countTrendData() {
this.updateCountChart()
}
},
methods: {
initAmountChart() {
if (!this.$refs.amountChartRef) return
this.amountChartInstance = echarts.init(this.$refs.amountChartRef)
this.updateAmountChart()
},
updateAmountChart() {
if (!this.amountChartInstance) return
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params) => {
const data = params[0]
const point = this.amountTrendPoints[data.dataIndex]
return `${data.name}<br/>金额: ${this.formatAmount(point.value)}`
}
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
top: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: this.amountTrendPoints.map(p => p.label.replace('最近', '近').replace('天', '日')),
axisLabel: {
rotate: 45,
fontSize: 12,
color: '#666'
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: (value) => {
return this.formatAmount(value)
},
fontSize: 12,
color: '#666'
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
},
splitLine: {
lineStyle: {
color: '#f0f0f0'
}
}
},
series: [
{
name: '交易金额',
type: 'bar',
data: this.amountTrendPoints.map(p => p.value),
barWidth: '25%',
barMinHeight: 2,
itemStyle: {
color: '#10b981',
borderRadius: [4, 4, 0, 0]
},
label: {
show: true,
position: 'top',
formatter: (params) => {
return this.formatAmount(params.value)
},
fontSize: 11,
color: '#333'
}
}
]
}
this.amountChartInstance.setOption(option)
},
initCountChart() {
if (!this.$refs.countChartRef) return
this.countChartInstance = echarts.init(this.$refs.countChartRef)
this.updateCountChart()
},
updateCountChart() {
if (!this.countChartInstance) return
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: (params) => {
const data = params[0]
const item = this.countTrendData[data.dataIndex]
return `${data.name}<br/>笔数: ${item.value}`
}
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
top: '5%',
containLabel: true
},
xAxis: {
type: 'category',
data: this.countTrendData.map(d => d.label),
axisLabel: {
rotate: 45,
fontSize: 12,
color: '#666'
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
}
},
yAxis: {
type: 'value',
axisLabel: {
formatter: '{value}笔',
fontSize: 12,
color: '#666'
},
axisLine: {
lineStyle: {
color: '#e0e0e0'
}
},
splitLine: {
lineStyle: {
color: '#f0f0f0'
}
}
},
series: [
{
name: '交易笔数',
type: 'bar',
data: this.countTrendData.map(d => d.value),
barWidth: '25%',
barMinHeight: 2,
itemStyle: {
color: '#10b981',
borderRadius: [4, 4, 0, 0]
},
label: {
show: true,
position: 'top',
formatter: (params) => {
return `${params.value}`
},
fontSize: 11,
color: '#333'
}
}
]
}
this.countChartInstance.setOption(option)
},
handleResize() {
if (this.amountChartInstance) {
this.amountChartInstance.resize()
}
if (this.countChartInstance) {
this.countChartInstance.resize()
}
},
parseIntervalValue(value) {
if (!value || value === '' || value === '-1') return 0
const num = parseInt(value)
@@ -696,7 +754,17 @@ export default {
return 'bg-red-500'
},
getRiskEventCardClass(riskLevel) {
if (riskLevel === 'high-risk') return 'bg-[#FFF0F0] border border-red-200'
if (riskLevel === 'medium-risk') return 'bg-[#FFF8E7] border border-[#F5D980]'
return 'bg-[#ECF9EF] border border-[#CAECD3]'
},
getRiskEventIcon(riskLevel) {
if (riskLevel === 'high-risk') return new URL('@/assets/images/report/gfx.png', import.meta.url).href
if (riskLevel === 'medium-risk') return new URL('@/assets/images/report/zfx.png', import.meta.url).href
return new URL('@/assets/images/report/zq.png', import.meta.url).href
}
}
}
</script>
@@ -721,6 +789,50 @@ export default {
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) {
.time-trend-analysis {