2025-11-17 12:49:59 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="card special-list-verification">
|
|
|
|
|
|
<!-- 头部标题和最终决策 -->
|
|
|
|
|
|
<div class="mb-4 relative">
|
|
|
|
|
|
<div class="flex items-center mb-3">
|
|
|
|
|
|
<div class="w-8 h-8 flex items-center justify-center mr-3">
|
|
|
|
|
|
<img src="@/assets/images/report/gazdryhycp.png" alt="特殊名单验证" class="w-8 h-8 object-contain" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span class="font-bold text-gray-800">特殊名单验证</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 最终评分 -->
|
|
|
|
|
|
<div v-if="finalWeight" class="bg-blue-50 rounded-lg p-4 border border-[#2B79EE8F] mb-3">
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<span class="text-sm text-gray-600">最终规则评分</span>
|
|
|
|
|
|
<span class="text-xl font-bold text-gray-800">{{ finalWeight }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 命中规则列表 -->
|
|
|
|
|
|
<div v-if="hitRules.length > 0">
|
|
|
|
|
|
<LTitle title="命中项目" />
|
|
|
|
|
|
<div class="mt-3 space-y-2">
|
|
|
|
|
|
<div v-for="rule in hitRules" :key="rule.ruleId"
|
|
|
|
|
|
class="bg-white rounded-xl p-4 border border-gray-200 relative">
|
|
|
|
|
|
<!-- <div class="absolute top-0 right-0">
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<div class="px-2 py-1 text-sm text-white rounded-bl-xl rounded-tr-xl bg-orange-500">
|
2025-11-17 12:49:59 +08:00
|
|
|
|
权重: {{ rule.weight }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div> -->
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<div class="w-10 h-10 mr-4">
|
|
|
|
|
|
<img :src="getRuleIcon()" alt="规则" class="w-10 h-10 object-contain" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
<div class="font-bold text-gray-800">{{ rule.name }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 标签页导航 -->
|
|
|
|
|
|
<div class="grid grid-cols-4 w-full border-b mb-5">
|
|
|
|
|
|
<button v-for="(tab, key) in {
|
|
|
|
|
|
summary: '汇总',
|
|
|
|
|
|
low: '短期逾期',
|
|
|
|
|
|
medium: '严重逾期',
|
|
|
|
|
|
high: '无法收回',
|
|
|
|
|
|
}" :key="key"
|
2025-12-18 15:39:43 +08:00
|
|
|
|
class="px-2 py-3 text-center cursor-pointer transition-all duration-300 font-medium text-sm sm:text-sm relative border-b-2"
|
2025-11-17 12:49:59 +08:00
|
|
|
|
:class="[
|
|
|
|
|
|
key === 'summary'
|
|
|
|
|
|
? activeTab === key
|
|
|
|
|
|
? 'border-blue-500 text-blue-600'
|
|
|
|
|
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
|
|
|
|
: key === 'low'
|
|
|
|
|
|
? activeTab === key
|
|
|
|
|
|
? 'border-blue-500 text-blue-600'
|
|
|
|
|
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
|
|
|
|
: key === 'medium'
|
|
|
|
|
|
? activeTab === key
|
|
|
|
|
|
? 'border-orange-500 text-orange-600'
|
|
|
|
|
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
|
|
|
|
: activeTab === key
|
|
|
|
|
|
? 'border-red-500 text-red-600'
|
|
|
|
|
|
: 'border-transparent text-gray-500 hover:text-gray-700',
|
|
|
|
|
|
]" @click="handlerTab(key)">
|
|
|
|
|
|
{{ tab }}
|
|
|
|
|
|
<span v-if="
|
|
|
|
|
|
key !== 'summary' &&
|
|
|
|
|
|
summaryData.byRiskLevel &&
|
|
|
|
|
|
summaryData.byRiskLevel.find(level => level.id === key && level.triggered > 0)
|
|
|
|
|
|
" :class="[
|
2025-12-18 15:39:43 +08:00
|
|
|
|
'absolute -top-1 -right-1 inline-flex items-center justify-center w-4 h-4 text-sm font-bold leading-none text-white rounded-full',
|
2025-11-17 12:49:59 +08:00
|
|
|
|
key === 'low' ? 'bg-blue-500' : key === 'medium' ? 'bg-orange-500' : 'bg-red-500',
|
|
|
|
|
|
]">
|
|
|
|
|
|
{{summaryData.byRiskLevel.find(level => level.id === key).triggered}}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 标签页内容 -->
|
|
|
|
|
|
<div class="mt-2">
|
|
|
|
|
|
<!-- 汇总页 -->
|
|
|
|
|
|
<div v-if="activeTab === 'summary'" class="space-y-6">
|
|
|
|
|
|
<!-- 风险级别汇总卡片 -->
|
|
|
|
|
|
<div class="grid grid-cols-1 gap-4">
|
|
|
|
|
|
<div v-for="levelSummary in summaryData.byRiskLevel" :key="levelSummary.id" :class="[
|
|
|
|
|
|
'bg-white rounded-xl border border-gray-200 p-4 transition-all duration-300 hover:shadow-lg cursor-pointer relative',
|
|
|
|
|
|
levelSummary.id === 'low'
|
|
|
|
|
|
? 'border-l-4 border-l-blue-500'
|
|
|
|
|
|
: levelSummary.id === 'medium'
|
|
|
|
|
|
? 'border-l-4 border-l-orange-500'
|
|
|
|
|
|
: 'border-l-4 border-l-red-500',
|
|
|
|
|
|
]" @click="handleRiskLevelClick(levelSummary.id)">
|
|
|
|
|
|
<div class="absolute top-0 right-0">
|
|
|
|
|
|
<div :class="[
|
2025-12-18 15:39:43 +08:00
|
|
|
|
'px-2 py-1 text-sm text-white rounded-bl-xl rounded-tr-xl',
|
2025-11-17 12:49:59 +08:00
|
|
|
|
levelSummary.triggered > 0
|
|
|
|
|
|
? levelSummary.id === 'low'
|
|
|
|
|
|
? 'bg-blue-500'
|
|
|
|
|
|
: levelSummary.id === 'medium'
|
|
|
|
|
|
? 'bg-orange-500'
|
|
|
|
|
|
: 'bg-red-500'
|
|
|
|
|
|
: 'bg-gray-400',
|
|
|
|
|
|
]">
|
|
|
|
|
|
{{ levelSummary.triggered > 0 ? '已命中' : '未命中' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center mb-3">
|
|
|
|
|
|
<div class="w-8 h-8 flex items-center justify-center mr-3">
|
|
|
|
|
|
<img :src="getRiskLevelIcon(levelSummary.id)" :alt="levelSummary.label"
|
|
|
|
|
|
class="w-8 h-8 object-contain" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h3 class="text-base font-semibold" :class="levelSummary.id === 'low'
|
|
|
|
|
|
? 'text-blue-700'
|
|
|
|
|
|
: levelSummary.id === 'medium'
|
|
|
|
|
|
? 'text-orange-700'
|
|
|
|
|
|
: 'text-red-700'
|
|
|
|
|
|
">
|
|
|
|
|
|
{{ levelSummary.label }}
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mt-3 flex items-end justify-between">
|
|
|
|
|
|
<div>
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<p class="text-sm text-gray-600">命中项</p>
|
2025-11-17 12:49:59 +08:00
|
|
|
|
<p class="text-xl font-bold" :class="levelSummary.triggered > 0
|
|
|
|
|
|
? levelSummary.id === 'low'
|
|
|
|
|
|
? 'text-blue-600'
|
|
|
|
|
|
: levelSummary.id === 'medium'
|
|
|
|
|
|
? 'text-orange-600'
|
|
|
|
|
|
: 'text-red-600'
|
|
|
|
|
|
: 'text-gray-500'
|
|
|
|
|
|
">
|
|
|
|
|
|
{{ levelSummary.triggered }} / {{ levelSummary.total }}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
2025-12-18 15:39:43 +08:00
|
|
|
|
class="text-sm px-3 py-1.5 rounded-full focus:outline-none transition-all duration-300"
|
2025-11-17 12:49:59 +08:00
|
|
|
|
:class="levelSummary.id === 'low'
|
|
|
|
|
|
? 'bg-blue-100 text-blue-600 hover:bg-blue-200'
|
|
|
|
|
|
: levelSummary.id === 'medium'
|
|
|
|
|
|
? 'bg-orange-100 text-orange-600 hover:bg-orange-200'
|
|
|
|
|
|
: 'bg-red-100 text-red-600 hover:bg-red-200'
|
|
|
|
|
|
" @click.stop="handleRiskLevelClick(levelSummary.id)">
|
|
|
|
|
|
查看详情
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 风险类型汇总 -->
|
|
|
|
|
|
<div class="">
|
|
|
|
|
|
<LTitle title="风险主体分布" />
|
|
|
|
|
|
<div class="space-y-3 mt-3">
|
|
|
|
|
|
<div v-for="typeSummary in summaryData.byRiskType" :key="typeSummary.id"
|
|
|
|
|
|
class="p-4 bg-white rounded-xl border border-gray-200 shadow-sm">
|
|
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
|
<div class="text-sm font-medium text-gray-900">
|
|
|
|
|
|
{{ typeSummary.label }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center space-x-1">
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<span class="text-sm text-gray-500">命中项</span>
|
|
|
|
|
|
<span class="text-sm font-medium px-1.5 py-0.5 rounded-full" :class="[
|
2025-11-17 12:49:59 +08:00
|
|
|
|
typeSummary.triggered > 0
|
|
|
|
|
|
? getRateColor(typeSummary.triggered, typeSummary.total) === 'red'
|
|
|
|
|
|
? 'bg-red-100 text-red-700'
|
|
|
|
|
|
: getRateColor(typeSummary.triggered, typeSummary.total) === 'orange'
|
|
|
|
|
|
? 'bg-orange-100 text-orange-700'
|
|
|
|
|
|
: 'bg-blue-100 text-blue-700'
|
|
|
|
|
|
: 'bg-gray-100 text-gray-500',
|
|
|
|
|
|
]">
|
|
|
|
|
|
{{ typeSummary.triggered }}
|
|
|
|
|
|
</span>
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<span class="text-sm text-gray-500">/</span>
|
|
|
|
|
|
<span class="text-sm text-gray-500">{{ typeSummary.total }}</span>
|
2025-11-17 12:49:59 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="w-full h-2 bg-gray-100 rounded-full mt-2 overflow-hidden">
|
|
|
|
|
|
<div class="h-full rounded-full transition-all duration-500" :class="[
|
|
|
|
|
|
typeSummary.triggered > 0
|
|
|
|
|
|
? getRateColor(typeSummary.triggered, typeSummary.total) === 'red'
|
|
|
|
|
|
? 'bg-red-500'
|
|
|
|
|
|
: getRateColor(typeSummary.triggered, typeSummary.total) === 'orange'
|
|
|
|
|
|
? 'bg-orange-500'
|
|
|
|
|
|
: 'bg-blue-500'
|
|
|
|
|
|
: 'bg-gray-200',
|
|
|
|
|
|
]" :style="{
|
|
|
|
|
|
width: `${(typeSummary.triggered / Math.max(1, typeSummary.total)) * 100}%`,
|
|
|
|
|
|
}"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 金融机构汇总 -->
|
|
|
|
|
|
<div class="">
|
|
|
|
|
|
<LTitle title="机构风险分布" />
|
|
|
|
|
|
<div class="grid grid-cols-2 gap-3 mt-3">
|
|
|
|
|
|
<div v-for="institutionSummary in summaryData.byInstitution" :key="institutionSummary.id"
|
|
|
|
|
|
class="flex flex-col p-4 rounded-xl border border-gray-200 bg-white shadow-sm">
|
|
|
|
|
|
<div class="flex items-center mb-2">
|
|
|
|
|
|
<div class="mr-2 flex-shrink-0 w-8 h-8 flex items-center justify-center rounded-full"
|
|
|
|
|
|
:class="[
|
|
|
|
|
|
institutionSummary.triggered > 0
|
|
|
|
|
|
? getRateColor(institutionSummary.triggered, institutionSummary.total) === 'red'
|
|
|
|
|
|
? 'bg-red-100'
|
|
|
|
|
|
: getRateColor(institutionSummary.triggered, institutionSummary.total) === 'orange'
|
|
|
|
|
|
? 'bg-orange-100'
|
|
|
|
|
|
: 'bg-blue-100'
|
|
|
|
|
|
: 'bg-gray-100',
|
|
|
|
|
|
]">
|
|
|
|
|
|
<span class="text-sm font-bold" :class="[
|
|
|
|
|
|
institutionSummary.triggered > 0
|
|
|
|
|
|
? getRateColor(institutionSummary.triggered, institutionSummary.total) === 'red'
|
|
|
|
|
|
? 'text-red-600'
|
|
|
|
|
|
: getRateColor(institutionSummary.triggered, institutionSummary.total) === 'orange'
|
|
|
|
|
|
? 'text-orange-600'
|
|
|
|
|
|
: 'text-blue-600'
|
|
|
|
|
|
: 'text-gray-500',
|
|
|
|
|
|
]">
|
|
|
|
|
|
{{ institutionSummary.triggered }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex flex-col">
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<h4 class="text-sm font-medium text-gray-900 truncate max-w-[100px]">
|
2025-11-17 12:49:59 +08:00
|
|
|
|
{{ institutionSummary.label }}
|
|
|
|
|
|
</h4>
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<div class="flex items-center mt-1 text-sm text-gray-500">
|
2025-11-17 12:49:59 +08:00
|
|
|
|
<span>命中项:{{ institutionSummary.triggered }}/{{ institutionSummary.total
|
|
|
|
|
|
}}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="w-full h-1.5 bg-gray-100 rounded-full">
|
|
|
|
|
|
<div class="h-full rounded-full transition-all duration-500" :class="[
|
|
|
|
|
|
institutionSummary.triggered > 0
|
|
|
|
|
|
? getRateColor(institutionSummary.triggered, institutionSummary.total) === 'red'
|
|
|
|
|
|
? 'bg-red-500'
|
|
|
|
|
|
: getRateColor(institutionSummary.triggered, institutionSummary.total) === 'orange'
|
|
|
|
|
|
? 'bg-orange-500'
|
|
|
|
|
|
: 'bg-blue-500'
|
|
|
|
|
|
: 'bg-gray-200',
|
|
|
|
|
|
]" :style="{
|
|
|
|
|
|
width: `${(institutionSummary.triggered / Math.max(1, institutionSummary.total)) * 100}%`,
|
|
|
|
|
|
}"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 风险详情页 -->
|
|
|
|
|
|
<div v-else>
|
|
|
|
|
|
<div class="mb-3">
|
|
|
|
|
|
<h3 class="text-lg font-semibold" :class="activeTab === 'low' ? 'text-blue-700' : activeTab === 'medium' ? 'text-orange-700' : 'text-red-700'
|
|
|
|
|
|
">
|
|
|
|
|
|
{{ tabConfigs[activeTab].title }}
|
|
|
|
|
|
</h3>
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<p class="text-sm text-gray-600 mt-1">
|
2025-11-17 12:49:59 +08:00
|
|
|
|
{{ tabConfigs[activeTab].description }}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 风险详情 - 身份证风险 -->
|
|
|
|
|
|
<div class="mb-4">
|
|
|
|
|
|
<h4 class="text-base font-medium text-gray-700 mb-2">
|
|
|
|
|
|
{{ riskTypeConfigs.idCard.title }}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
<div v-for="item in processedData[activeTab].filter(i => i.riskType === 'idCard')"
|
|
|
|
|
|
:key="item.id" :class="[
|
|
|
|
|
|
'rounded-xl p-4 relative',
|
|
|
|
|
|
item.isTriggered
|
|
|
|
|
|
? item.levelType === 'low'
|
|
|
|
|
|
? 'bg-[#E3F2FD] border border-blue-200'
|
|
|
|
|
|
: item.levelType === 'medium'
|
|
|
|
|
|
? 'bg-[#FFF3E0] border border-orange-200'
|
|
|
|
|
|
: 'bg-[#FFF0F0] border border-red-200'
|
|
|
|
|
|
: 'bg-[#F0FFF0] border border-green-200',
|
|
|
|
|
|
]">
|
|
|
|
|
|
<div class="absolute top-0 right-0">
|
|
|
|
|
|
<div :class="[
|
2025-12-18 15:39:43 +08:00
|
|
|
|
'px-2 py-1 text-sm text-white rounded-bl-xl rounded-tr-xl',
|
2025-11-17 12:49:59 +08:00
|
|
|
|
item.isTriggered
|
|
|
|
|
|
? item.levelType === 'low'
|
|
|
|
|
|
? 'bg-blue-500'
|
|
|
|
|
|
: item.levelType === 'medium'
|
|
|
|
|
|
? 'bg-orange-500'
|
|
|
|
|
|
: 'bg-red-500'
|
|
|
|
|
|
: 'bg-[#4CAF50]',
|
|
|
|
|
|
]">
|
|
|
|
|
|
{{ item.isTriggered ? '命中' : '无' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<div class="w-10 h-10 mr-4">
|
|
|
|
|
|
<img :src="getItemIcon(item.isTriggered, item.levelType)"
|
|
|
|
|
|
:alt="item.institutionLabel" class="w-10 h-10 object-contain" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
<div class="text-sm font-bold text-gray-800">
|
|
|
|
|
|
{{ item.institutionLabel }}{{ item.levelLabel }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<div class="grid grid-cols-2 gap-2 text-sm mt-3">
|
2025-11-17 12:49:59 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<span class="text-gray-500">发生次数:</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
:class="item.isTriggered ? (item.levelType === 'low' ? 'text-blue-600' : item.levelType === 'medium' ? 'text-orange-600' : 'text-red-600') + ' font-medium' : 'text-gray-500'">
|
|
|
|
|
|
{{ item.count === '0' || !item.count ? '-' : item.count }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span class="text-gray-500">最近发生:</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
:class="item.isTriggered ? (item.levelType === 'low' ? 'text-blue-600' : item.levelType === 'medium' ? 'text-orange-600' : 'text-red-600') + ' font-medium' : 'text-gray-500'">
|
|
|
|
|
|
{{ item.time && item.time !== '0' ? `近${item.time}年内` : '-' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="processedData[activeTab].filter(i => i.riskType === 'idCard').length === 0"
|
|
|
|
|
|
class="p-3 text-center text-sm text-gray-500 bg-gray-50 rounded-xl border border-gray-200">
|
|
|
|
|
|
{{ riskTypeConfigs.idCard.emptyText }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 风险详情 - 手机号风险 -->
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h4 class="text-base font-medium text-gray-700 mb-2">
|
|
|
|
|
|
{{ riskTypeConfigs.mobile.title }}
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
|
<div v-for="item in processedData[activeTab].filter(i => i.riskType === 'mobile')"
|
|
|
|
|
|
:key="item.id" :class="[
|
|
|
|
|
|
'rounded-xl p-4 relative',
|
|
|
|
|
|
item.isTriggered
|
|
|
|
|
|
? item.levelType === 'low'
|
|
|
|
|
|
? 'bg-[#E3F2FD] border border-blue-200'
|
|
|
|
|
|
: item.levelType === 'medium'
|
|
|
|
|
|
? 'bg-[#FFF3E0] border border-orange-200'
|
|
|
|
|
|
: 'bg-[#FFF0F0] border border-red-200'
|
|
|
|
|
|
: 'bg-[#F0FFF0] border border-green-200',
|
|
|
|
|
|
]">
|
|
|
|
|
|
<div class="absolute top-0 right-0">
|
|
|
|
|
|
<div :class="[
|
2025-12-18 15:39:43 +08:00
|
|
|
|
'px-2 py-1 text-sm text-white rounded-bl-xl rounded-tr-xl',
|
2025-11-17 12:49:59 +08:00
|
|
|
|
item.isTriggered
|
|
|
|
|
|
? item.levelType === 'low'
|
|
|
|
|
|
? 'bg-blue-500'
|
|
|
|
|
|
: item.levelType === 'medium'
|
|
|
|
|
|
? 'bg-orange-500'
|
|
|
|
|
|
: 'bg-red-500'
|
|
|
|
|
|
: 'bg-[#4CAF50]',
|
|
|
|
|
|
]">
|
|
|
|
|
|
{{ item.isTriggered ? '命中' : '无' }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center">
|
|
|
|
|
|
<div class="w-10 h-10 mr-4">
|
|
|
|
|
|
<img :src="getItemIcon(item.isTriggered, item.levelType)"
|
|
|
|
|
|
:alt="item.institutionLabel" class="w-10 h-10 object-contain" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
|
<div class="text-sm font-bold text-gray-800">
|
|
|
|
|
|
{{ item.institutionLabel }}{{ item.levelLabel }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-12-18 15:39:43 +08:00
|
|
|
|
<div class="grid grid-cols-2 gap-2 text-sm mt-3">
|
2025-11-17 12:49:59 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<span class="text-gray-500">发生次数:</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
:class="item.isTriggered ? (item.levelType === 'low' ? 'text-blue-600' : item.levelType === 'medium' ? 'text-orange-600' : 'text-red-600') + ' font-medium' : 'text-gray-500'">
|
|
|
|
|
|
{{ item.count === '0' || !item.count ? '-' : item.count }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span class="text-gray-500">最近发生:</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
:class="item.isTriggered ? (item.levelType === 'low' ? 'text-blue-600' : item.levelType === 'medium' ? 'text-orange-600' : 'text-red-600') + ' font-medium' : 'text-gray-500'">
|
|
|
|
|
|
{{ item.time && item.time !== '0' ? `近${item.time}年内` : '-' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="processedData[activeTab].filter(i => i.riskType === 'mobile').length === 0"
|
|
|
|
|
|
class="p-3 text-center text-sm text-gray-500 bg-gray-50 rounded-xl border border-gray-200">
|
|
|
|
|
|
{{ riskTypeConfigs.mobile.emptyText }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue'
|
|
|
|
|
|
import LTitle from '@/components/LTitle.vue'
|
|
|
|
|
|
import { useRiskNotifier } from '@/composables/useRiskNotifier'
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
data: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
default: () => ({})
|
|
|
|
|
|
},
|
|
|
|
|
|
apiId: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
index: {
|
|
|
|
|
|
type: Number,
|
|
|
|
|
|
default: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
notifyRiskStatus: {
|
|
|
|
|
|
type: Function,
|
|
|
|
|
|
default: () => { },
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 当前激活的标签页
|
|
|
|
|
|
const activeTab = ref('summary')
|
|
|
|
|
|
|
|
|
|
|
|
// 风险类型定义
|
|
|
|
|
|
const riskTypes = {
|
|
|
|
|
|
idCard: {
|
|
|
|
|
|
label: '身份证风险',
|
|
|
|
|
|
prefix: 'id_',
|
|
|
|
|
|
},
|
|
|
|
|
|
mobile: {
|
|
|
|
|
|
label: '手机号风险',
|
|
|
|
|
|
prefix: 'cell_',
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 金融机构定义
|
|
|
|
|
|
const institutions = {
|
|
|
|
|
|
court_bad: {
|
|
|
|
|
|
label: '法院失信人',
|
|
|
|
|
|
levels: ['bad'],
|
|
|
|
|
|
},
|
|
|
|
|
|
court_executed: {
|
|
|
|
|
|
label: '法院被执行人',
|
|
|
|
|
|
levels: [''], // 特殊情况,没有风险级别后缀
|
|
|
|
|
|
},
|
|
|
|
|
|
bank: {
|
|
|
|
|
|
label: '银行(含信用卡)',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
nbank: {
|
|
|
|
|
|
label: '非银机构',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
nbank_nsloan: {
|
|
|
|
|
|
label: '持牌网络小贷',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
nbank_sloan: {
|
|
|
|
|
|
label: '持牌小贷',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
nbank_cons: {
|
|
|
|
|
|
label: '持牌消费金融',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
nbank_finlea: {
|
|
|
|
|
|
label: '持牌融资租赁',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
nbank_autofin: {
|
|
|
|
|
|
label: '持牌汽车金融',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
nbank_other: {
|
|
|
|
|
|
label: '其他',
|
|
|
|
|
|
levels: ['bad', 'overdue', 'lost'],
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 风险级别定义
|
|
|
|
|
|
const riskLevels = {
|
|
|
|
|
|
'': { label: '', type: 'medium', color: 'orange' }, // 特殊情况:法院被执行人
|
|
|
|
|
|
overdue: { label: '一般风险', type: 'low', color: 'blue' },
|
|
|
|
|
|
bad: { label: '中风险', type: 'medium', color: 'orange' },
|
|
|
|
|
|
lost: { label: '高风险', type: 'high', color: 'red' },
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 风险级别分组
|
|
|
|
|
|
const riskGroups = {
|
|
|
|
|
|
low: { label: '短期逾期', color: 'blue' },
|
|
|
|
|
|
medium: { label: '严重逾期', color: 'orange' },
|
|
|
|
|
|
high: { label: '无法收回', color: 'red' },
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 标签页配置
|
|
|
|
|
|
const tabConfigs = {
|
|
|
|
|
|
summary: {
|
|
|
|
|
|
title: '风险汇总',
|
|
|
|
|
|
description: '展示各类风险汇总信息',
|
|
|
|
|
|
},
|
|
|
|
|
|
low: {
|
|
|
|
|
|
title: '短期逾期详情',
|
|
|
|
|
|
description: '展示所有短期逾期相关记录',
|
|
|
|
|
|
},
|
|
|
|
|
|
medium: {
|
|
|
|
|
|
title: '严重逾期详情',
|
|
|
|
|
|
description: '展示所有严重逾期相关记录',
|
|
|
|
|
|
},
|
|
|
|
|
|
high: {
|
|
|
|
|
|
title: '无法收回详情',
|
|
|
|
|
|
description: '展示所有无法收回相关记录',
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 风险类型配置
|
|
|
|
|
|
const riskTypeConfigs = {
|
|
|
|
|
|
idCard: {
|
|
|
|
|
|
title: '身份证风险信息',
|
|
|
|
|
|
emptyText: '暂无身份证风险记录',
|
|
|
|
|
|
},
|
|
|
|
|
|
mobile: {
|
|
|
|
|
|
title: '手机号风险信息',
|
|
|
|
|
|
emptyText: '暂无手机号风险记录',
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理数据并分类
|
|
|
|
|
|
function processData(data) {
|
|
|
|
|
|
const result = {
|
|
|
|
|
|
low: [],
|
|
|
|
|
|
medium: [],
|
|
|
|
|
|
high: [],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 id 和 cell 数据
|
|
|
|
|
|
const idData = data.id || {}
|
|
|
|
|
|
const cellData = data.cell || {}
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历风险类型(身份证/手机号)
|
|
|
|
|
|
Object.entries(riskTypes).forEach(([riskTypeKey, riskType]) => {
|
|
|
|
|
|
const sourceData = riskTypeKey === 'idCard' ? idData : cellData
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历金融机构
|
|
|
|
|
|
Object.entries(institutions).forEach(([institutionKey, institution]) => {
|
|
|
|
|
|
// 遍历当前机构支持的风险级别
|
|
|
|
|
|
institution.levels.forEach(levelKey => {
|
|
|
|
|
|
// 构建字段名 - 特殊处理court_bad和court_executed
|
|
|
|
|
|
let fieldBase
|
|
|
|
|
|
if (institutionKey === 'court_bad') {
|
|
|
|
|
|
fieldBase = `court_bad`
|
|
|
|
|
|
} else if (institutionKey === 'court_executed') {
|
|
|
|
|
|
fieldBase = `court_executed`
|
|
|
|
|
|
} else {
|
|
|
|
|
|
fieldBase = `${institutionKey}_${levelKey}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const valueField = fieldBase
|
|
|
|
|
|
const timeField = `${fieldBase}_time`
|
|
|
|
|
|
const countField = `${fieldBase}_allnum`
|
|
|
|
|
|
|
|
|
|
|
|
// 获取值(0表示命中,空表示未命中)
|
|
|
|
|
|
const value = sourceData[valueField] || ''
|
|
|
|
|
|
const time = sourceData[timeField] || '0'
|
|
|
|
|
|
const count = sourceData[countField] || '0'
|
|
|
|
|
|
|
|
|
|
|
|
// 创建记录对象
|
|
|
|
|
|
const record = {
|
|
|
|
|
|
id: `${riskTypeKey}_${institutionKey}_${levelKey || 'executed'}`,
|
|
|
|
|
|
riskType: riskTypeKey,
|
|
|
|
|
|
riskTypeLabel: riskType.label,
|
|
|
|
|
|
institution: institutionKey,
|
|
|
|
|
|
institutionLabel: institution.label,
|
|
|
|
|
|
level: levelKey || 'executed',
|
|
|
|
|
|
levelLabel: institutionKey === 'court_executed' ? '' : riskLevels[levelKey].label,
|
|
|
|
|
|
levelType: institutionKey === 'court_executed' ? 'medium' : riskLevels[levelKey].type,
|
|
|
|
|
|
levelColor: institutionKey === 'court_executed' ? 'orange' : riskLevels[levelKey].color,
|
|
|
|
|
|
value: value,
|
|
|
|
|
|
time: time,
|
|
|
|
|
|
count: count,
|
|
|
|
|
|
// 根据文档,0表示命中,空表示未命中
|
|
|
|
|
|
isTriggered: value === '0',
|
|
|
|
|
|
fieldName: valueField,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据风险级别分类
|
|
|
|
|
|
if (institutionKey === 'court_executed' || institutionKey === 'court_bad') {
|
|
|
|
|
|
result['medium'].push(record)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
result[riskLevels[levelKey].type].push(record)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成汇总统计数据
|
|
|
|
|
|
function generateSummary(processedData) {
|
|
|
|
|
|
const summary = {
|
|
|
|
|
|
// 按风险级别统计
|
|
|
|
|
|
byRiskLevel: Object.keys(riskGroups).map(levelKey => {
|
|
|
|
|
|
const items = processedData[levelKey]
|
|
|
|
|
|
const triggeredItems = items.filter(item => item.isTriggered)
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: levelKey,
|
|
|
|
|
|
label: riskGroups[levelKey].label,
|
|
|
|
|
|
color: riskGroups[levelKey].color,
|
|
|
|
|
|
total: items.length,
|
|
|
|
|
|
triggered: triggeredItems.length,
|
|
|
|
|
|
percentage: items.length > 0 ? ((triggeredItems.length / items.length) * 100).toFixed(1) : 0,
|
|
|
|
|
|
items: triggeredItems,
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
|
|
// 按风险类型统计
|
|
|
|
|
|
byRiskType: Object.keys(riskTypes).map(typeKey => {
|
|
|
|
|
|
const allItems = [...processedData.low, ...processedData.medium, ...processedData.high].filter(
|
|
|
|
|
|
item => item.riskType === typeKey
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const triggeredItems = allItems.filter(item => item.isTriggered)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: typeKey,
|
|
|
|
|
|
label: riskTypes[typeKey].label,
|
|
|
|
|
|
total: allItems.length,
|
|
|
|
|
|
triggered: triggeredItems.length,
|
|
|
|
|
|
percentage: allItems.length > 0 ? ((triggeredItems.length / allItems.length) * 100).toFixed(1) : 0,
|
|
|
|
|
|
items: triggeredItems,
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
|
|
// 按机构类型统计
|
|
|
|
|
|
byInstitution: Object.keys(institutions).map(institutionKey => {
|
|
|
|
|
|
const allItems = [...processedData.low, ...processedData.medium, ...processedData.high].filter(
|
|
|
|
|
|
item => item.institution === institutionKey
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
const triggeredItems = allItems.filter(item => item.isTriggered)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: institutionKey,
|
|
|
|
|
|
label: institutions[institutionKey].label,
|
|
|
|
|
|
total: allItems.length,
|
|
|
|
|
|
triggered: triggeredItems.length,
|
|
|
|
|
|
percentage: allItems.length > 0 ? ((triggeredItems.length / allItems.length) * 100).toFixed(1) : 0,
|
|
|
|
|
|
items: triggeredItems,
|
|
|
|
|
|
}
|
|
|
|
|
|
}),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return summary
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 持有处理后的数据
|
|
|
|
|
|
const processedData = ref({})
|
|
|
|
|
|
const summaryData = ref({})
|
|
|
|
|
|
|
|
|
|
|
|
// 获取原始数据
|
|
|
|
|
|
const rawData = computed(() => {
|
|
|
|
|
|
return props.data?.data?.data || props.data?.data || props.data || {}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 最终决策和评分
|
|
|
|
|
|
const finalDecision = computed(() => {
|
|
|
|
|
|
return rawData.value.Rule_final_decision || ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const finalWeight = computed(() => {
|
|
|
|
|
|
return rawData.value.Rule_final_weight || ''
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 解析命中规则
|
|
|
|
|
|
const hitRules = computed(() => {
|
|
|
|
|
|
const rules = []
|
|
|
|
|
|
const data = rawData.value
|
|
|
|
|
|
|
|
|
|
|
|
// 遍历所有可能的规则字段
|
|
|
|
|
|
// Rule_name_odr* 和 Rule_weight_odr* 是动态的
|
|
|
|
|
|
const ruleNamePattern = /^Rule_name_(odr\d+)$/
|
|
|
|
|
|
const ruleWeightPattern = /^Rule_weight_(odr\d+)$/
|
|
|
|
|
|
|
|
|
|
|
|
// 收集所有规则名称
|
|
|
|
|
|
const ruleMap = {}
|
|
|
|
|
|
Object.keys(data).forEach(key => {
|
|
|
|
|
|
const nameMatch = key.match(ruleNamePattern)
|
|
|
|
|
|
if (nameMatch) {
|
|
|
|
|
|
const ruleId = nameMatch[1]
|
|
|
|
|
|
if (!ruleMap[ruleId]) {
|
|
|
|
|
|
ruleMap[ruleId] = { ruleId, name: '', weight: '' }
|
|
|
|
|
|
}
|
|
|
|
|
|
ruleMap[ruleId].name = data[key] || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const weightMatch = key.match(ruleWeightPattern)
|
|
|
|
|
|
if (weightMatch) {
|
|
|
|
|
|
const ruleId = weightMatch[1]
|
|
|
|
|
|
if (!ruleMap[ruleId]) {
|
|
|
|
|
|
ruleMap[ruleId] = { ruleId, name: '', weight: '' }
|
|
|
|
|
|
}
|
|
|
|
|
|
ruleMap[ruleId].weight = data[key] || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 转换为数组,只包含有名称的规则
|
|
|
|
|
|
Object.values(ruleMap).forEach(rule => {
|
|
|
|
|
|
if (rule.name) {
|
|
|
|
|
|
rules.push(rule)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return rules
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到指定标签页
|
|
|
|
|
|
function handlerTab(tabName) {
|
|
|
|
|
|
activeTab.value = tabName
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 点击风险级别时跳转到对应标签页
|
|
|
|
|
|
function handleRiskLevelClick(levelType) {
|
|
|
|
|
|
handlerTab(levelType)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据命中率确定颜色
|
|
|
|
|
|
function getRateColor(triggered, total) {
|
|
|
|
|
|
if (total === 0) return 'gray'
|
|
|
|
|
|
const rate = triggered / total
|
|
|
|
|
|
if (rate === 0) return 'gray'
|
|
|
|
|
|
if (rate < 0.3) return 'blue'
|
|
|
|
|
|
if (rate < 0.6) return 'orange'
|
|
|
|
|
|
return 'red'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getDecisionClass(decision) {
|
|
|
|
|
|
const map = {
|
|
|
|
|
|
'Accept': 'bg-[#4CAF50]',
|
|
|
|
|
|
'Reject': 'bg-[#E53935]',
|
|
|
|
|
|
'Review': 'bg-orange-500',
|
|
|
|
|
|
}
|
|
|
|
|
|
return map[decision] || 'bg-gray-500'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取规则图标
|
|
|
|
|
|
function getRuleIcon() {
|
|
|
|
|
|
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取风险级别图标
|
|
|
|
|
|
function getRiskLevelIcon(levelId) {
|
|
|
|
|
|
const iconMap = {
|
|
|
|
|
|
'low': () => new URL('@/assets/images/report/zq.png', import.meta.url).href,
|
|
|
|
|
|
'medium': () => new URL('@/assets/images/report/zfx.png', import.meta.url).href,
|
|
|
|
|
|
'high': () => new URL('@/assets/images/report/gfx.png', import.meta.url).href,
|
|
|
|
|
|
}
|
|
|
|
|
|
return iconMap[levelId] ? iconMap[levelId]() : iconMap['medium']()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取项目图标
|
|
|
|
|
|
function getItemIcon(isTriggered, levelType) {
|
|
|
|
|
|
if (!isTriggered) {
|
|
|
|
|
|
return new URL('@/assets/images/report/zq.png', import.meta.url).href
|
|
|
|
|
|
}
|
|
|
|
|
|
if (levelType === 'low') {
|
|
|
|
|
|
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
|
|
|
|
|
|
}
|
|
|
|
|
|
if (levelType === 'high') {
|
|
|
|
|
|
return new URL('@/assets/images/report/gfx.png', import.meta.url).href
|
|
|
|
|
|
}
|
|
|
|
|
|
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算风险评分(0-100分,分数越高越安全)
|
|
|
|
|
|
const riskScore = computed(() => {
|
|
|
|
|
|
if (!summaryData.value || !summaryData.value.byRiskLevel) return 100
|
|
|
|
|
|
|
|
|
|
|
|
const highRiskCount = summaryData.value.byRiskLevel.find(item => item.id === 'high')?.triggered || 0
|
|
|
|
|
|
const mediumRiskCount = summaryData.value.byRiskLevel.find(item => item.id === 'medium')?.triggered || 0
|
|
|
|
|
|
const lowRiskCount = summaryData.value.byRiskLevel.find(item => item.id === 'low')?.triggered || 0
|
|
|
|
|
|
|
|
|
|
|
|
const totalTriggered = highRiskCount + mediumRiskCount + lowRiskCount
|
|
|
|
|
|
|
|
|
|
|
|
if (totalTriggered === 0) {
|
|
|
|
|
|
return 100 // 无风险
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据风险级别计算分数,高风险权重更高
|
|
|
|
|
|
const score = Math.max(10, 100 - (highRiskCount * 30 + mediumRiskCount * 15 + lowRiskCount * 5))
|
|
|
|
|
|
return score
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 composable 通知父组件风险评分
|
|
|
|
|
|
useRiskNotifier(props, riskScore)
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 数据可能在不同层级,尝试多种路径
|
|
|
|
|
|
const data = props.data?.data?.data || props.data?.data || props.data || {}
|
|
|
|
|
|
const processed = processData(data)
|
|
|
|
|
|
processedData.value = processed
|
|
|
|
|
|
summaryData.value = generateSummary(processed)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 暴露给父组件
|
|
|
|
|
|
defineExpose({
|
|
|
|
|
|
riskScore
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.special-list-verification {
|
|
|
|
|
|
@apply space-y-4;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|