Files
tydata-webview-v2/report-viewer/src/ui/DWBG6A2C/components/RiskListSection.vue

415 lines
12 KiB
Vue
Raw Normal View History

2025-11-13 22:27:54 +08:00
<template>
<div class="card">
<div class="rounded-lg border border-gray-200 pb-4 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/fxmd.png" alt="风险名单" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">风险名单</span>
</div>
<div class="mt-4">
<!-- 名单核查概览 -->
<div class="mb-4">
<div class="mb-3">
<LTitle title="名单核查概览" />
</div>
<div class="px-4">
<div class="grid grid-cols-2 gap-4">
<div class="rounded-lg border p-2 text-center" :class="getOverviewCardStyle()">
<div class="text-xl font-bold mb-1" :class="getOverviewTextStyle()">
{{ riskCount }}
</div>
<div class="text-sm font-medium text-gray-700">命中名单数</div>
</div>
<div class="rounded-lg border p-2 text-center" :class="getOverviewCardStyle()">
<div class="text-xl font-bold mb-1" :class="getOverviewTextStyle()">
{{ riskCount > 0 ? '有风险' : '无风险' }}
</div>
<div class="text-sm font-medium text-gray-700">综合评估</div>
</div>
</div>
</div>
</div>
<!-- 详细风险名单项目 -->
<div class="space-y-3 p-4">
<div v-for="(item, key) in riskListItems" :key="key"
class="relative rounded-lg border p-3 transition-all duration-300" :class="getRiskListItemCardStyle(item)">
<!-- 右上角命中状态标签 -->
<div class="absolute top-0 right-0">
<div class="px-2 py-1 text-xs text-white rounded-bl-lg rounded-tr-lg" :class="getRiskListTagClass(item)">
{{ item.hasRisk ? '命中' : '未命中' }}
</div>
</div>
<div class="flex items-center">
<div class="w-10 h-10 flex items-center justify-center mr-3">
<img :src="getRiskListItemIcon(item)" :alt="item.name" class="w-10 h-10 object-contain" />
</div>
<div class="flex-1">
<div class="flex items-center mb-1">
<div class="font-bold text-gray-800">{{ item.name }}</div>
<!-- 风险等级标签 -->
<div class="ml-2">
<div class="px-2 py-1 text-xs font-medium rounded-md" :class="getSeverityBadgeStyle(item.severity)">
{{ getSeverityText(item.severity) }}
</div>
</div>
</div>
<div class="text-sm text-[#999999]">{{ item.description }}</div>
</div>
</div>
</div>
</div>
<!-- 高风险警告 -->
<div v-if="hasHighRiskItems" class="px-4">
<div class="flex items-center mb-3">
<div class="w-4 h-4 flex items-center justify-center mr-2">
<img src="@/assets/images/report/wxts_icon.png" alt="风险提示" class="w-4 h-4 object-contain" />
</div>
<div class="font-bold text-[#EB3C3C]">高风险名单警告</div>
</div>
<div class="ml-6 text-sm mb-1 text-[#EB3C3C]">
命中多个高风险名单建议立即停止业务并进行详细核实
</div>
<div class="ml-6 text-sm text-[#EB3C3C]">
命中名单{{ highRiskItemNames.join('、') }}
</div>
</div>
<!-- 安全提示 -->
<div v-if="!riskCount" class="px-4 mt-4">
<div class="flex items-center mb-3">
<div class="w-4 h-4 flex items-center justify-center mr-2">
<img src="@/assets/images/report/wxts_icon.png" alt="安全提示" class="w-4 h-4 object-contain" />
</div>
<div class="font-bold text-gray-800">风险名单核查通过</div>
</div>
<div class="ml-6 text-sm text-gray-600">
未命中任何风险名单各项指标正常
</div>
</div>
</div>
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="风险名单展示申请人是否命中各类风险名单,包括黑名单、灰名单和其他风险标识。数据来源于各类风险数据库和监管机构。命中风险名单可能涉及欺诈、赖账、违法违规等行为。不同类型的风险名单有不同的风险等级和处理建议。建议对命中任何风险名单的申请进行重点审查,并根据具体情况调整风险策略。" />
</template>
<script setup>
import { computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
const props = defineProps({
data: {
type: Object,
required: true,
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
// 从data中解构出需要的字段
const riskList = computed(() => {
return props.data?.riskList || {}
})
// 风险名单项目配置
const riskListConfig = {
creditLeaseRisk: {
name: '信用租赁风险名单',
description: '命中信用租赁相关风险名单',
icon: '🏠',
severity: 'high',
color: 'red'
},
bankOverdueRecord: {
name: '银行逾期记录',
description: '命中银行逾期记录名单',
icon: '🏦',
severity: 'high',
color: 'red'
},
riskPhoneNumber: {
name: '通信风险手机号库',
description: '命中风险手机号码库',
icon: '📱',
severity: 'medium',
color: 'orange'
},
highRiskArea: {
name: '高风险集中地区',
description: '命中高风险地区名单',
icon: '🗺️',
severity: 'medium',
color: 'orange'
},
creditOverdueRecord: {
name: '信贷逾期记录',
description: '命中信贷逾期记录名单',
icon: '💰',
severity: 'high',
color: 'red'
},
vehicleLeaseViolation: {
name: '车辆租赁违约',
description: '命中车辆租赁违约名单',
icon: '🚗',
severity: 'medium',
color: 'orange'
},
courtViolator: {
name: '法院被执行名单',
description: '命中法院被执行人名单',
icon: '⚖️',
severity: 'high',
color: 'red'
},
industryBlacklist: {
name: '行业黑名单',
description: '命中行业相关黑名单',
icon: '🚫',
severity: 'high',
color: 'red'
},
identityFake: {
name: '身份伪冒风险',
description: '命中身份伪冒风险名单',
icon: '🎭',
severity: 'high',
color: 'red'
},
groupFraud: {
name: '团伙欺诈名单',
description: '命中团伙欺诈相关名单',
icon: '👥',
severity: 'high',
color: 'red'
},
taxDebt: {
name: '欠税名单',
description: '命中税务欠税名单',
icon: '💸',
severity: 'medium',
color: 'orange'
},
2026-07-08 21:29:20 +08:00
mediumRelationRisk: {
name: '中介关联风险',
description: '命中中介关联风险名单',
icon: '🔗',
severity: 'high',
color: 'red'
},
2025-11-13 22:27:54 +08:00
phoneNumberStatus: {
name: '手机号码状态异常',
description: '手机号码状态存在异常',
icon: '📞',
severity: 'low',
color: 'yellow'
}
}
// 处理风险名单数据
const riskListItems = computed(() => {
const items = {}
Object.keys(riskListConfig).forEach(key => {
const value = riskList.value?.[key] || 0
const hasRisk = value > 0
items[key] = {
...riskListConfig[key],
hasRisk,
value
}
})
return items
})
// 计算风险项数量
const riskCount = computed(() => {
return Object.values(riskListItems.value).filter(item => item.hasRisk).length
})
// 是否有高风险项目
const hasHighRiskItems = computed(() => {
return Object.values(riskListItems.value).some(item =>
item.hasRisk && item.severity === 'high'
)
})
// 高风险项目名称
const highRiskItemNames = computed(() => {
return Object.values(riskListItems.value)
.filter(item => item.hasRisk && item.severity === 'high')
.map(item => item.name)
})
// 是否有中风险项目
const hasMediumRiskItems = computed(() => {
return Object.values(riskListItems.value).some(item =>
item.hasRisk && item.severity === 'medium'
)
})
// 获取标题图标
const getTitleIcon = () => {
if (hasHighRiskItems.value || hasMediumRiskItems.value) {
return new URL('@/assets/images/report/gfx.png', import.meta.url).href
}
return new URL('@/assets/images/report/qsgg.png', import.meta.url).href
}
// 获取概览卡片样式 - 根据风险情况动态变化
const getOverviewCardStyle = () => {
if (riskCount.value === 0) {
return 'border-[#4CAF508F] bg-[#4CAF501A]' // 无风险:绿色
} else if (hasHighRiskItems.value) {
return 'border-[#EB3C3C8F] bg-[#EB3C3C1A]' // 高风险:红色
} else {
return 'border-[#D6943E8F] bg-[#D6943E1A]' // 中风险:橙色
}
}
// 获取概览文字样式 - 根据风险情况动态变化
const getOverviewTextStyle = () => {
if (riskCount.value === 0) {
return 'text-[#4CAF50]' // 无风险:绿色
} else if (hasHighRiskItems.value) {
return 'text-[#EB3C3C]' // 高风险:红色
} else {
return 'text-[#D6943E]' // 中风险:橙色
}
}
// 获取风险名单项目卡片整体样式
const getRiskListItemCardStyle = (item) => {
if (item.hasRisk) {
if (item.severity === 'high') {
return 'bg-[#F9ECEC] border-[#F0CACA]'
} else if (item.severity === 'medium') {
return 'bg-[#F9F5ED] border-[#FFB74D8F]'
} else {
return 'bg-[#FFF9E6] border-[#FFE6A0]'
}
}
return 'bg-[#ECF9EF] border-[#CAECD3]'
}
// 获取风险名单项目图标
const getRiskListItemIcon = (item) => {
if (item.hasRisk) {
if (item.severity === 'high') {
return new URL('@/assets/images/report/gfx.png', import.meta.url).href
} else if (item.severity === 'medium') {
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
} else {
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
}
}
return new URL('@/assets/images/report/zq.png', import.meta.url).href
}
// 获取风险名单标签样式(命中/未命中)
const getRiskListTagClass = (item) => {
if (item.hasRisk) {
if (item.severity === 'high') {
return 'bg-[#E53935]' // 高风险命中:红色
} else if (item.severity === 'medium') {
return 'bg-[#FF8C00]' // 中风险命中:橙色
} else {
return 'bg-[#FFA726]' // 低风险命中:浅橙色
}
}
return 'bg-[#4CAF50]' // 未命中:绿色
}
// 获取严重程度标签样式
const getSeverityBadgeStyle = (severity) => {
const styleMap = {
'high': 'text-[#EB3C3C] bg-[#EB3C3C1A]',
'medium': 'text-[#D6943E] bg-[#D6943E1A]',
'low': 'text-[#FFA726] bg-[#FFA7261A]'
}
return styleMap[severity] || 'text-[#999999] bg-[#9999991A]'
}
// 获取严重程度文本
const getSeverityText = (severity) => {
const textMap = {
'high': '高',
'medium': '中',
'low': '低'
}
return textMap[severity] || '未知'
}
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
// 计算高风险数量、中风险数量和低风险数量
let highRiskCount = 0;
let mediumRiskCount = 0;
let lowRiskCount = 0;
Object.values(riskListItems.value).forEach(item => {
if (item.hasRisk) {
if (item.severity === 'high') {
highRiskCount++;
} else if (item.severity === 'medium') {
mediumRiskCount++;
} else if (item.severity === 'low') {
lowRiskCount++;
}
}
});
// 没有风险 -> 100分
if (highRiskCount === 0 && mediumRiskCount === 0 && lowRiskCount === 0) {
return 100;
}
// 计算风险分数
let score = 100;
// 每个高风险扣18分风险名单风险很高
score -= highRiskCount * 18;
// 每个中风险扣10分
score -= mediumRiskCount * 10;
// 每个低风险扣5分
score -= lowRiskCount * 5;
// 确保分数在合理范围内
return Math.max(20, Math.min(100, score));
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped>
// 组件样式</style>