ff
This commit is contained in:
1150
public/example.json
1150
public/example.json
File diff suppressed because one or more lines are too long
@@ -579,8 +579,21 @@ const featureMap = {
|
|||||||
remark: '名下车辆车牌查询B展示查询对象名下登记的车辆车牌号、车牌颜色、车辆类型等信息,数据来源于车辆管理部门等权威机构,仅供参考。',
|
remark: '名下车辆车牌查询B展示查询对象名下登记的车辆车牌号、车牌颜色、车辆类型等信息,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
IVYZ4Y27: {
|
||||||
|
name: "学历信息高级版",
|
||||||
|
component: defineAsyncComponent(() => import("@/ui/IVYZ4Y27/index.vue")),
|
||||||
|
remark: '名下车辆车牌查询B展示查询对象名下登记的车辆车牌号、车牌颜色、车辆类型等信息,数据来源于车辆管理部门等权威机构,仅供参考。',
|
||||||
|
},
|
||||||
|
|
||||||
|
DWBG5SAM: {
|
||||||
|
name: "天远指谜报告",
|
||||||
|
component: defineAsyncComponent(() => import("@/ui/DWBG5SAM/index.vue")),
|
||||||
|
remark: '天远指谜报告综合展示身份核验、信用等级、风险画像与名单、公安不良、逾期与司法案件等维度,数据来源于合作机构,仅供参考。',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const maskValue = computed(() => {
|
const maskValue = computed(() => {
|
||||||
return (type, value) => {
|
return (type, value) => {
|
||||||
if (!value) return value;
|
if (!value) return value;
|
||||||
@@ -670,6 +683,8 @@ const featureRiskLevels = {
|
|||||||
'YYSY7D3E': 5, // 手机携号转网
|
'YYSY7D3E': 5, // 手机携号转网
|
||||||
'YYSY8B1C': 5, // 手机在网时长
|
'YYSY8B1C': 5, // 手机在网时长
|
||||||
'CFLX3A9B': 5, // 法院被执行人限高版
|
'CFLX3A9B': 5, // 法院被执行人限高版
|
||||||
|
'IVYZ4Y27' :3 , //xueli
|
||||||
|
'DWBG5SAM': 10,
|
||||||
|
|
||||||
// 🟡 中风险类 - 权重 5
|
// 🟡 中风险类 - 权重 5
|
||||||
'QYGL3F8E': 5, // 人企关系加强版
|
'QYGL3F8E': 5, // 人企关系加强版
|
||||||
|
|||||||
261
src/ui/DWBG5SAM/components/JudiciaryCaseSection.vue
Normal file
261
src/ui/DWBG5SAM/components/JudiciaryCaseSection.vue
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tyzm-section">
|
||||||
|
<div class="section-title">司法案件信息</div>
|
||||||
|
|
||||||
|
<!-- 案件概览 -->
|
||||||
|
<div class="sub-title">
|
||||||
|
案件概览
|
||||||
|
<span class="tag" :class="riskLevelCls(courtInfoRiskLevel)">{{ riskLevelText(courtInfoRiskLevel) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="info-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>被告案件数</th>
|
||||||
|
<td>{{ fmtCount(co.beigaoTotalCasesCounts, '件') }}</td>
|
||||||
|
<th>未结案数</th>
|
||||||
|
<td>{{ fmtCount(co.beigaoTotalWeiCaseCounts, '件') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>执行案件数</th>
|
||||||
|
<td>{{ fmtCount(co.executionCaseCounts, '件') }}</td>
|
||||||
|
<th>涉案金额(被告)</th>
|
||||||
|
<td>{{ co.beigaoTotalCaseAmounts != null ? `${co.beigaoTotalCaseAmounts} 万元` : '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>失信命中</th>
|
||||||
|
<td>
|
||||||
|
<span class="tag" :class="co.disinCaseCounts ? 'tag-danger' : 'tag-success'">
|
||||||
|
{{ co.disinCaseCounts ? '命中' : '未命中' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<th>限高命中</th>
|
||||||
|
<td>
|
||||||
|
<span class="tag" :class="co.limitCaseCounts ? 'tag-danger' : 'tag-success'">
|
||||||
|
{{ co.limitCaseCounts ? '命中' : '未命中' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>关联企业</th>
|
||||||
|
<td>{{ co.affiliateCompany ?? '—' }}</td>
|
||||||
|
<th>最近案件时间</th>
|
||||||
|
<td>{{ co.leastCaseTime || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 案件统计 -->
|
||||||
|
<div class="sub-title">案件统计</div>
|
||||||
|
<div class="grid-4">
|
||||||
|
<div class="mini-card" v-for="item in caseTypeMetrics" :key="item.label"
|
||||||
|
:class="{ 'card-danger': item.count > 0 }">
|
||||||
|
<div class="mini-label">{{ item.label }}</div>
|
||||||
|
<div class="mini-value" :class="item.count > 0 ? 'text-danger' : ''">{{ item.count }} 件</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 案件详细列表 -->
|
||||||
|
<div v-for="section in caseSections" :key="section.key">
|
||||||
|
<div class="sub-title" v-if="section.items && section.items.length">
|
||||||
|
{{ section.label }}
|
||||||
|
<span class="tag tag-info">{{ section.items.length }} 件</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="section.items && section.items.length" class="case-list">
|
||||||
|
<div v-for="(c, i) in section.items" :key="i"
|
||||||
|
class="case-item"
|
||||||
|
:class="{ danger: c.nssdw === '被告' || c.nssdw === '被执行人' }">
|
||||||
|
<div class="case-header">
|
||||||
|
<span class="case-number">{{ c.caseNumber || '—' }}</span>
|
||||||
|
<span class="tag" :class="statusCls(c.caseStatus)">{{ c.caseStatus || '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="case-body">
|
||||||
|
<p>
|
||||||
|
<span class="case-label">诉讼地位:</span>{{ c.nssdw || '—' }}
|
||||||
|
<span class="case-label ml-4">案件类型:</span>{{ c.najlx || '—' }}
|
||||||
|
</p>
|
||||||
|
<p v-if="c.nlaayTree">
|
||||||
|
<span class="case-label">案由:</span>{{ c.nlaayTree }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="case-label">立案时间:</span>{{ c.dlarq || '—' }}
|
||||||
|
<span class="case-label ml-4">结案时间:</span>{{ c.djarq || '—' }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class="case-label">经办法院:</span>{{ c.njbfy || '—' }}
|
||||||
|
</p>
|
||||||
|
<p v-if="c.njabdje">
|
||||||
|
<span class="case-label">结案标的金额:</span>{{ c.njabdje }}
|
||||||
|
</p>
|
||||||
|
<p v-if="c.njafs">
|
||||||
|
<span class="case-label">结案方式:</span>{{ c.njafs }}
|
||||||
|
</p>
|
||||||
|
<p v-if="c.nssdw === '被告' || c.nssdw === '被执行人'">
|
||||||
|
<span class="case-label">胜诉估计:</span>
|
||||||
|
<span class="tag" :class="victoryCls(c.npjVictory)">{{ c.npjVictory || '—' }}</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="c.nsqzxbdje">
|
||||||
|
<span class="case-label">申请执行标的金额:</span>{{ c.nsqzxbdje }}
|
||||||
|
</p>
|
||||||
|
<p v-if="c.nwzxje">
|
||||||
|
<span class="case-label">未执行金额:</span>{{ c.nwzxje }}
|
||||||
|
</p>
|
||||||
|
<p v-if="c.cahHx">
|
||||||
|
<span class="case-label">后续案件:</span>{{ c.cahHx }}
|
||||||
|
</p>
|
||||||
|
<p v-if="c.cdsrxx" class="text-xs text-gray-400 mt-2" style="word-break:break-all">
|
||||||
|
<span class="case-label">当事人:</span>{{ truncate(c.cdsrxx, 200) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 案件分布统计 -->
|
||||||
|
<div v-if="caseCounts" class="sub-title">案件分布统计</div>
|
||||||
|
<table v-if="caseCounts" class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>统计维度</th>
|
||||||
|
<th>被告案件数</th>
|
||||||
|
<th>被告案件金额</th>
|
||||||
|
<th>原告案件数</th>
|
||||||
|
<th>原告案件金额</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>总计</td>
|
||||||
|
<td>{{ caseCounts.beigaoTotalCasesCounts || '—' }}</td>
|
||||||
|
<td>{{ caseCounts.beigaoTotalCaseAmounts || '—' }}</td>
|
||||||
|
<td>{{ caseCounts.yuangaoTotalCasesCounts || '—' }}</td>
|
||||||
|
<td>{{ caseCounts.yuangaoTotalCaseAmounts || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="caseCounts.caseActionDistribution && caseCounts.caseActionDistribution !== '-'">
|
||||||
|
<td>案由分布</td>
|
||||||
|
<td colspan="4">{{ caseCounts.caseActionDistribution }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="caseCounts.localDistribution && caseCounts.localDistribution !== '-'">
|
||||||
|
<td>地区分布</td>
|
||||||
|
<td colspan="4">{{ caseCounts.localDistribution }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="caseCounts.timeDistribution && caseCounts.timeDistribution !== '-'">
|
||||||
|
<td>时间分布</td>
|
||||||
|
<td colspan="4">{{ caseCounts.timeDistribution }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div v-if="!hasData" class="empty-hint">暂无司法案件数据</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const co = computed(() => props.data.caseOverviewInfo || {});
|
||||||
|
const ci = computed(() => props.data.courtInfo || {});
|
||||||
|
const courtInfoRiskLevel = computed(() => props.data.courtInfoRiskLevel || '');
|
||||||
|
const mci = computed(() => ci.value.newMultCourtInfo || {});
|
||||||
|
const caseCounts = computed(() => mci.value.caseCounts || null);
|
||||||
|
const hasData = computed(() => Object.keys(props.data).length > 0);
|
||||||
|
|
||||||
|
const riskLevelCls = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return 'tag-danger';
|
||||||
|
if (lv === '2' || lv === 2) return 'tag-warning';
|
||||||
|
return 'tag-success';
|
||||||
|
};
|
||||||
|
const riskLevelText = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return '高风险';
|
||||||
|
if (lv === '2' || lv === 2) return '中风险';
|
||||||
|
return '低风险';
|
||||||
|
};
|
||||||
|
|
||||||
|
const fmtCount = (v, unit) => {
|
||||||
|
if (v == null || v === '') return '—';
|
||||||
|
return `${v} ${unit}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusCls = (s) => {
|
||||||
|
if (!s) return '';
|
||||||
|
if (s.includes('未结')) return 'tag-warning';
|
||||||
|
if (s.includes('已结')) return 'tag-success';
|
||||||
|
return 'tag-info';
|
||||||
|
};
|
||||||
|
|
||||||
|
const victoryCls = (v) => {
|
||||||
|
if (v === '胜诉') return 'tag-success';
|
||||||
|
if (v === '败诉') return 'tag-danger';
|
||||||
|
if (v === '部分胜诉') return 'tag-warning';
|
||||||
|
return 'tag-info';
|
||||||
|
};
|
||||||
|
|
||||||
|
const truncate = (str, len) => {
|
||||||
|
if (!str) return '—';
|
||||||
|
return str.length > len ? str.slice(0, len) + '...' : str;
|
||||||
|
};
|
||||||
|
|
||||||
|
const caseTypeMetrics = computed(() => [
|
||||||
|
{ label: '民事案件', count: ci.value.civilCasesCount || 0 },
|
||||||
|
{ label: '刑事案件', count: ci.value.criminalCasesCount || 0 },
|
||||||
|
{ label: '执行案件', count: ci.value.enforcementCasesCount || 0 },
|
||||||
|
{ label: '行政案件', count: ci.value.administrativeCasesCount || 0 },
|
||||||
|
{ label: '非诉保全', count: ci.value.preservationCasesCount || 0 },
|
||||||
|
{ label: '赔偿案件', count: ci.value.compensationCasesCount || 0 },
|
||||||
|
{ label: '破产案件', count: ci.value.bankruptcyCasesCount || 0 },
|
||||||
|
{ label: '管辖案件', count: ci.value.supervisionCasesCount || 0 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const caseSections = computed(() => {
|
||||||
|
const m = mci.value;
|
||||||
|
return [
|
||||||
|
{ key: 'civil', label: '民事案件', items: m.civilCases || [] },
|
||||||
|
{ key: 'enforcement', label: '执行案件', items: m.enforcementCases || [] },
|
||||||
|
{ key: 'criminal', label: '刑事案件', items: m.criminalCases || [] },
|
||||||
|
{ key: 'administrative', label: '行政案件', items: m.administrativeCases || [] },
|
||||||
|
{ key: 'preservation', label: '非诉保全审查', items: m.preservationCases || [] },
|
||||||
|
{ key: 'supervision', label: '管辖案件', items: m.supervisionCases || [] },
|
||||||
|
{ key: 'compensation', label: '赔偿案件', items: m.compensationCases || [] },
|
||||||
|
{ key: 'bankruptcy', label: '破产案件', items: m.bankruptcyCases || [] },
|
||||||
|
{ key: 'disin', label: '失信公告', items: m.disinCases || [] },
|
||||||
|
{ key: 'limit', label: '限高公告', items: m.limitCases || [] },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '../shared.scss';
|
||||||
|
.grid-4 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mini-card {
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
&.card-danger { background: #fff5f5; }
|
||||||
|
}
|
||||||
|
.mini-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.mini-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.text-danger { color: #d32f2f; }
|
||||||
|
.tag-info { background: #e8f0fe; color: #1a73e8; }
|
||||||
|
.ml-4 { margin-left: 16px; }
|
||||||
|
.text-xs { font-size: 12px; }
|
||||||
|
.text-gray-400 { color: #9ca3af; }
|
||||||
|
.mt-2 { margin-top: 8px; }
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid-4 { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
111
src/ui/DWBG5SAM/components/LeasingBehaviorSection.vue
Normal file
111
src/ui/DWBG5SAM/components/LeasingBehaviorSection.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tyzm-section">
|
||||||
|
<div class="section-title">租赁行为</div>
|
||||||
|
|
||||||
|
<!-- 租赁行为 -->
|
||||||
|
<div class="sub-title">
|
||||||
|
租赁行为
|
||||||
|
<span class="tag" :class="riskLevelCls(reRiskLevel)">{{ riskLevelText(reRiskLevel) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>时间范围</th>
|
||||||
|
<th>申请机构数</th>
|
||||||
|
<th>申请次数</th>
|
||||||
|
<th>周末申请机构数</th>
|
||||||
|
<th>周末申请次数</th>
|
||||||
|
<th>夜间申请机构数</th>
|
||||||
|
<th>夜间申请次数</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in rentalRows" :key="item.label">
|
||||||
|
<td>{{ item.label }}</td>
|
||||||
|
<td>{{ item.inst }}</td>
|
||||||
|
<td>{{ item.cnt }}</td>
|
||||||
|
<td>{{ item.wkInst }}</td>
|
||||||
|
<td>{{ item.wkCnt }}</td>
|
||||||
|
<td>{{ item.ntInst }}</td>
|
||||||
|
<td>{{ item.ntCnt }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 关联风险监督 -->
|
||||||
|
<div class="sub-title">
|
||||||
|
关联风险监督
|
||||||
|
<span class="tag" :class="riskLevelCls(supRiskLevel)">{{ riskLevelText(supRiskLevel) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="info-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>最后申请时间</th>
|
||||||
|
<td>{{ rs.leastTime || '—' }}</td>
|
||||||
|
<th>同一身份证关联手机号数</th>
|
||||||
|
<td>{{ rs.rentalRPhones ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>同一手机号关联身份证数</th>
|
||||||
|
<td>{{ rs.rentalRCards ?? '—' }}</td>
|
||||||
|
<th>详情</th>
|
||||||
|
<td>{{ rs.details || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div v-if="!hasData" class="empty-hint">暂无租赁行为数据</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const reRiskLevel = computed(() => props.data.reRiskLevel || '');
|
||||||
|
const supRiskLevel = computed(() => props.data.supRiskLevel || '');
|
||||||
|
const rb = computed(() => props.data.rentalBehavior || {});
|
||||||
|
const rs = computed(() => props.data.riskSupervision || {});
|
||||||
|
const hasData = computed(() => Object.keys(props.data).length > 0);
|
||||||
|
|
||||||
|
const riskLevelCls = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return 'tag-danger';
|
||||||
|
if (lv === '2' || lv === 2) return 'tag-warning';
|
||||||
|
return 'tag-success';
|
||||||
|
};
|
||||||
|
const riskLevelText = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return '高风险';
|
||||||
|
if (lv === '2' || lv === 2) return '中风险';
|
||||||
|
return '低风险';
|
||||||
|
};
|
||||||
|
|
||||||
|
const periods = [
|
||||||
|
{ key: '3d', label: '近3天' },
|
||||||
|
{ key: '7d', label: '近7天' },
|
||||||
|
{ key: '14d', label: '近14天' },
|
||||||
|
{ key: '1m', label: '近1个月' },
|
||||||
|
{ key: '3m', label: '近3个月' },
|
||||||
|
{ key: '6m', label: '近6个月' },
|
||||||
|
{ key: '12m', label: '近12个月' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const rentalRows = computed(() => {
|
||||||
|
const b = rb.value;
|
||||||
|
return periods.map(p => ({
|
||||||
|
label: p.label,
|
||||||
|
inst: b[`rentInst${p.key}`] || '—',
|
||||||
|
cnt: b[`rentCnt${p.key}`] || '—',
|
||||||
|
wkInst: b[`rentInst${p.key}Wk`] || '—',
|
||||||
|
wkCnt: b[`rentCnt${p.key}Wk`] || '—',
|
||||||
|
ntInst: b[`rentInst${p.key}Nt`] || '—',
|
||||||
|
ntCnt: b[`rentCnt${p.key}Nt`] || '—',
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '../shared.scss';
|
||||||
|
</style>
|
||||||
209
src/ui/DWBG5SAM/components/LoanEvaluationSection.vue
Normal file
209
src/ui/DWBG5SAM/components/LoanEvaluationSection.vue
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tyzm-section">
|
||||||
|
<div class="section-title">借贷评估产品 - 命中详情</div>
|
||||||
|
|
||||||
|
<!-- 查询多头概览 -->
|
||||||
|
<div class="sub-title">
|
||||||
|
查询多头概览
|
||||||
|
<span class="tag" :class="riskLevelCls(qmriskLevel)">{{ riskLevelText(qmriskLevel) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 命中次数统计 -->
|
||||||
|
<div class="sub-section-label">命中次数统计</div>
|
||||||
|
<div class="grid-3">
|
||||||
|
<div class="mini-card" v-for="item in hitMetrics" :key="item.label">
|
||||||
|
<div class="mini-label">{{ item.label }}</div>
|
||||||
|
<div class="mini-value" :class="Number(item.value) > 0 ? 'text-danger' : ''">{{ item.value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 近6个月贷款机构数 -->
|
||||||
|
<div class="sub-section-label">近6个月贷款机构数</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>时间范围</th>
|
||||||
|
<th>贷款机构数</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="item in loanOrgMetrics" :key="item.label">
|
||||||
|
<td>{{ item.label }}</td>
|
||||||
|
<td>{{ item.value || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 各机构多头查询表现 -->
|
||||||
|
<div class="sub-title">
|
||||||
|
各机构多头查询表现
|
||||||
|
<span class="tag" :class="riskLevelCls(qriskLevel)">{{ riskLevelText(qriskLevel) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 机构类型汇总 -->
|
||||||
|
<div class="sub-section-label">机构查询汇总(机构数 / 次数)</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>机构类型</th>
|
||||||
|
<th>近7天</th>
|
||||||
|
<th>近30天</th>
|
||||||
|
<th>近90天</th>
|
||||||
|
<th>近180天</th>
|
||||||
|
<th>近1年</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in institutionSummary" :key="row.name">
|
||||||
|
<td>{{ row.name }}</td>
|
||||||
|
<td>{{ row.d7 }}</td>
|
||||||
|
<td>{{ row.d30 }}</td>
|
||||||
|
<td>{{ row.d90 }}</td>
|
||||||
|
<td>{{ row.d180 }}</td>
|
||||||
|
<td>{{ row.d365 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 夜间查询 -->
|
||||||
|
<div class="sub-section-label">夜间查询汇总(机构数 / 次数)</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>机构类型</th>
|
||||||
|
<th>近7天</th>
|
||||||
|
<th>近30天</th>
|
||||||
|
<th>近90天</th>
|
||||||
|
<th>近180天</th>
|
||||||
|
<th>近1年</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in nightSummary" :key="row.name">
|
||||||
|
<td>{{ row.name }}</td>
|
||||||
|
<td>{{ row.d7 }}</td>
|
||||||
|
<td>{{ row.d30 }}</td>
|
||||||
|
<td>{{ row.d90 }}</td>
|
||||||
|
<td>{{ row.d180 }}</td>
|
||||||
|
<td>{{ row.d365 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div v-if="!hasData" class="empty-hint">暂无借贷评估数据</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const mm = computed(() => props.data.moreMuti || {});
|
||||||
|
const lease = computed(() => props.data.leaseIndexLoanSituation || {});
|
||||||
|
const qmriskLevel = computed(() => props.data.qmriskLevel || '');
|
||||||
|
const qriskLevel = computed(() => props.data.qriskLevel || '');
|
||||||
|
const hasData = computed(() => Object.keys(props.data).length > 0);
|
||||||
|
|
||||||
|
const riskLevelCls = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return 'tag-danger';
|
||||||
|
if (lv === '2' || lv === 2) return 'tag-warning';
|
||||||
|
return 'tag-success';
|
||||||
|
};
|
||||||
|
const riskLevelText = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return '高风险';
|
||||||
|
if (lv === '2' || lv === 2) return '中风险';
|
||||||
|
return '低风险';
|
||||||
|
};
|
||||||
|
|
||||||
|
const hitMetrics = computed(() => {
|
||||||
|
const m = mm.value;
|
||||||
|
return [
|
||||||
|
{ label: '历史关注名单命中', value: m.hallOrgHit || '0' },
|
||||||
|
{ label: '近180天关注名单', value: m.hallOrg180Days || '0' },
|
||||||
|
{ label: '近365天关注名单', value: m.hallOrg365Days || '0' },
|
||||||
|
{ label: '历史反欺诈命中', value: m.hallOrgFraudHit || '0' },
|
||||||
|
{ label: '近180天反欺诈', value: m.hallOrgF180Days || '0' },
|
||||||
|
{ label: '近365天反欺诈', value: m.hallOrgF365Days || '0' },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const loanOrgMetrics = computed(() => {
|
||||||
|
const l = lease.value;
|
||||||
|
return [
|
||||||
|
{ label: '近1天', value: l.loanOrgCountDay },
|
||||||
|
{ label: '近7天', value: l.loanOrgCount7Days },
|
||||||
|
{ label: '近14天', value: l.loanOrgCount14Days },
|
||||||
|
{ label: '近21天', value: l.loanOrgCount21Days },
|
||||||
|
{ label: '近30天', value: l.loanOrgCount30Days },
|
||||||
|
{ label: '近90天', value: l.loanOrgCount90Days },
|
||||||
|
{ label: '近180天', value: l.loanOrgCount180Days },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const pair = (m, orgKey, numKey) => {
|
||||||
|
const org = m[orgKey] || '0';
|
||||||
|
const num = m[numKey] || '0';
|
||||||
|
return `${org} / ${num}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const institutionSummary = computed(() => {
|
||||||
|
const m = mm.value;
|
||||||
|
return [
|
||||||
|
{ name: '银行', d7: pair(m, 'mk7DaysOrg', 'mk7DaysNum'), d30: pair(m, 'mk30DaysOrg', 'mk30DaysNum'), d90: pair(m, 'mk90DaysOrg', 'mk90DaysNum'), d180: pair(m, 'mk180DaysOrg', 'mk180DaysNum'), d365: pair(m, 'mk1YearOrg', 'mk1YearNum') },
|
||||||
|
{ name: '大型商业银行', d7: pair(m, 'mdk7DaysOrg', 'mdk7DaysOrg'), d30: pair(m, 'mdk30DaysOrg', 'mdk30DaysOrg'), d90: pair(m, 'mdk90DaysOrg', 'mdk90DaysOrg'), d180: pair(m, 'mdk180DaysOrg', 'mdk180DaysOrg'), d365: pair(m, 'mdk1YearOrg', 'mdk1YearOrg') },
|
||||||
|
{ name: '城市商业银行', d7: pair(m, 'mu7DaysOrg', 'mu7DaysNum'), d30: pair(m, 'mu30DaysOrg', 'mu30DaysNum'), d90: pair(m, 'mu90DaysOrg', 'mu90DaysNum'), d180: pair(m, 'mu180DaysOrg', 'mu180DaysNum'), d365: pair(m, 'mu1YearOrg', 'mu1YearNum') },
|
||||||
|
{ name: '消金公司', d7: pair(m, 'muC7DaysOrg', 'muA7DaysNum'), d30: pair(m, 'muC30DaysOrg', 'muA30DaysNum'), d90: pair(m, 'muC90DaysOrg', 'muA90DaysNum'), d180: pair(m, 'muC180DaysOrg', 'muA180DaysNum'), d365: pair(m, 'muC1YearOrg', 'muA1YearNum') },
|
||||||
|
{ name: '小额贷款公司', d7: pair(m, 'muS7DaysOrg', 'muS7DaysNum'), d30: pair(m, 'muS30DaysOrg', 'muS30DaysNum'), d90: pair(m, 'muS90DaysOrg', 'muS90DaysNum'), d180: pair(m, 'muS180DaysOrg', 'muS180DaysNum'), d365: pair(m, 'muS1YearOrg', 'muS1YearNum') },
|
||||||
|
{ name: '网络小额贷款', d7: pair(m, 'muN7DaysOrg', 'muN7DaysNum'), d30: pair(m, 'muN30DaysOrg', 'muN30DaysNum'), d90: pair(m, 'muN90DaysOrg', 'muN90DaysNum'), d180: pair(m, 'muN180DaysOrg', 'muN180DaysNum'), d365: pair(m, 'muN1YearOrg', 'muN1YearNum') },
|
||||||
|
{ name: '金融平台', d7: pair(m, 'muF7DaysOrg', 'muF7DaysNum'), d30: pair(m, 'muF30DaysOrg', 'muF30DaysNum'), d90: pair(m, 'muF90DaysOrg', 'muF90DaysNum'), d180: pair(m, 'muF180DaysOrg', 'muF180DaysNum'), d365: pair(m, 'muF1YearOrg', 'muF1YearNum') },
|
||||||
|
{ name: '汽车金融', d7: pair(m, 'muQ7DaysOrg', 'muQ7DaysNum'), d30: pair(m, 'muQ30DaysOrg', 'muQ30DaysNum'), d90: pair(m, 'muQ90DaysOrg', 'muQ90DaysNum'), d180: pair(m, 'muQ180DaysOrg', 'muQ180DaysNum'), d365: pair(m, 'muQ1YearOrg', 'muQ1YearNum') },
|
||||||
|
{ name: '共计', d7: pair(m, 'muT7DaysOrg', 'muT7DaysNum'), d30: pair(m, 'muT30DaysOrg', 'muT30DaysNum'), d90: pair(m, 'muT90DaysOrg', 'muT90DaysNum'), d180: pair(m, 'muT180DaysOrg', 'muT180DaysNum'), d365: pair(m, 'muT1YearOrg', 'muT1YearNum') },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const nightSummary = computed(() => {
|
||||||
|
const m = mm.value;
|
||||||
|
return [
|
||||||
|
{ name: '大型商业银行', d7: pair(m, 'muBBank7DaysOrg', 'muBBank7DaysNum'), d30: pair(m, 'muBBank30DaysOrg', 'muBBank30DaysNum'), d90: pair(m, 'muBBank90DaysOrg', 'muBBank90DaysNum'), d180: pair(m, 'muBBank180DaysOrg', 'muBBank180DaysNum'), d365: pair(m, 'muBBank1YearOrg', 'muBBank1YearNum') },
|
||||||
|
{ name: '城市商业银行', d7: pair(m, 'muCBank7DaysOrg', 'muCBank7DaysNum'), d30: pair(m, 'muCBank30DaysOrg', 'muCBank30DaysNum'), d90: pair(m, 'muCBank90DaysOrg', 'muCBank90DaysNum'), d180: pair(m, 'muCBank180DaysOrg', 'muCBank180DaysNum'), d365: pair(m, 'muCBank1YearOrg', 'muCBank1YearNum') },
|
||||||
|
{ name: '非银', d7: pair(m, 'muFBank7DaysOrg', 'muFBank7DaysNum'), d30: pair(m, 'muFBank30DaysOrg', 'muFBank30DaysNum'), d90: pair(m, 'muFBank90DaysOrg', 'muFBank90DaysNum'), d180: pair(m, 'muFBank180DaysOrg', 'muFBank180DaysNum'), d365: pair(m, 'muFBank1YearOrg', 'muFBank1YearNum') },
|
||||||
|
{ name: '共计', d7: pair(m, 'muTotal7DaysOrg', 'muTotal7DaysNum'), d30: pair(m, 'muTotal30DaysOrg', 'muTotal30DaysNum'), d90: pair(m, 'muTotal90DaysOrg', 'muTotal90DaysNum'), d180: pair(m, 'muTotal180DaysOrg', 'muTotal180DaysNum'), d365: pair(m, 'muTotal1YearOrg', 'muTotal1YearNum') },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '../shared.scss';
|
||||||
|
.grid-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mini-card {
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.mini-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.mini-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.text-danger { color: #d32f2f; }
|
||||||
|
.sub-section-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 10px 0 8px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid-3 { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
205
src/ui/DWBG5SAM/components/OverdueRiskSection.vue
Normal file
205
src/ui/DWBG5SAM/components/OverdueRiskSection.vue
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tyzm-section">
|
||||||
|
<div class="section-title">逾期风险详情</div>
|
||||||
|
|
||||||
|
<!-- 逾期概览 -->
|
||||||
|
<table class="info-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>风险等级</th>
|
||||||
|
<td>
|
||||||
|
<span class="tag" :class="riskLevel(overRiskLevel).cls">{{ riskLevel(overRiskLevel).text }}</span>
|
||||||
|
</td>
|
||||||
|
<th>当前逾期状态</th>
|
||||||
|
<td>
|
||||||
|
<span class="tag" :class="overProduct.hasUnsetDue === '逾期' ? 'tag-danger' : 'tag-success'">
|
||||||
|
{{ overProduct.hasUnsetDue || '—' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>逾期机构数(探查)</th>
|
||||||
|
<td>{{ fmtCount(probe.currentOverdueOrgCount, '家') }}</td>
|
||||||
|
<th>当前逾期金额</th>
|
||||||
|
<td>{{ overProduct.curDueAmt || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>当前逾期机构数</th>
|
||||||
|
<td>{{ overProduct.curDueCnt || '—' }}</td>
|
||||||
|
<th>已结清机构数</th>
|
||||||
|
<td>{{ overProduct.settlCnt || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>贷款总机构</th>
|
||||||
|
<td>{{ overProduct.totalLoanInst || '—' }}</td>
|
||||||
|
<th>贷款已还款总金额</th>
|
||||||
|
<td>{{ overProduct.totalRepayAmt || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 逾期天数/期数概览 -->
|
||||||
|
<div class="sub-title">逾期天数 / 期数概览</div>
|
||||||
|
<div class="grid-3">
|
||||||
|
<div class="mini-card" v-for="item in overdueDayMetrics" :key="item.label">
|
||||||
|
<div class="mini-label">{{ item.label }}</div>
|
||||||
|
<div class="mini-value" :class="item.isZero ? '' : 'text-danger'">{{ item.value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 近期逾期分布 -->
|
||||||
|
<div class="sub-title">近期逾期分布</div>
|
||||||
|
<div class="grid-4">
|
||||||
|
<div class="mini-card" v-for="item in recentOverdueMetrics" :key="item.label">
|
||||||
|
<div class="mini-label">{{ item.label }}</div>
|
||||||
|
<div class="mini-value" :class="item.danger ? 'text-danger' : 'text-success'">{{ item.value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 探查逾期 -->
|
||||||
|
<div class="sub-title">探查逾期</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>风险等级</th>
|
||||||
|
<td>
|
||||||
|
<span class="tag" :class="riskLevel(proRiskLevel).cls">{{ riskLevel(proRiskLevel).text }}</span>
|
||||||
|
</td>
|
||||||
|
<th>当前逾期机构数</th>
|
||||||
|
<td>{{ fmtCount(probe.currentOverdueOrgCount, '家') }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>异常还款机构数</th>
|
||||||
|
<td>{{ probe.accExcCount ?? '—' }}</td>
|
||||||
|
<th>睡眠机构数</th>
|
||||||
|
<td>{{ probe.accSleepCount ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>当前履约机构数</th>
|
||||||
|
<td>{{ probe.currentlyPerformanceCount ?? '—' }}</td>
|
||||||
|
<th>当前履约笔数</th>
|
||||||
|
<td>{{ probe.countPerformanceCount ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>最大逾期金额</th>
|
||||||
|
<td>{{ probe.maxOverdueAmt || '—' }}</td>
|
||||||
|
<th>最大逾期天数</th>
|
||||||
|
<td>{{ probe.maxOverdueDays || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>最大履约金额</th>
|
||||||
|
<td>{{ probe.maxPerformanceAmt || '—' }}</td>
|
||||||
|
<th>最近逾期时间</th>
|
||||||
|
<td>{{ probe.latestOverdueTime || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>最近履约时间</th>
|
||||||
|
<td colspan="3">{{ probe.latestPerformanceTime || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 特殊名单验证 -->
|
||||||
|
<div v-if="specialList && specialList.length" class="sub-title">特殊名单验证
|
||||||
|
<span class="tag ml-2" :class="riskLevel(specialRiskLevel).cls">{{ riskLevel(specialRiskLevel).text }}</span>
|
||||||
|
</div>
|
||||||
|
<table v-if="specialList && specialList.length" class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:60%">命中内容</th>
|
||||||
|
<th style="width:40%">命中结果</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(item, i) in specialList" :key="i">
|
||||||
|
<td>{{ item.mz || '—' }}</td>
|
||||||
|
<td>{{ item.jg || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div v-else class="empty-hint">暂无特殊名单验证数据</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const overRiskLevel = computed(() => props.data.overRiskLevel || '');
|
||||||
|
const proRiskLevel = computed(() => props.data.proRiskLevel || '');
|
||||||
|
const specialRiskLevel = computed(() => props.data.specialRiskLevel || '');
|
||||||
|
const overProduct = computed(() => props.data.overdueRiskProduct || {});
|
||||||
|
const probe = computed(() => props.data.probeOverdueRecord || {});
|
||||||
|
const specialList = computed(() => props.data.specialListVerification || []);
|
||||||
|
|
||||||
|
const riskLevel = (level) => {
|
||||||
|
if (level === '1' || level === 1) return { text: '高风险', cls: 'tag-danger' };
|
||||||
|
if (level === '2' || level === 2) return { text: '中风险', cls: 'tag-warning' };
|
||||||
|
return { text: '低风险', cls: 'tag-success' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const fmtCount = (v, unit) => {
|
||||||
|
if (v == null || v === '') return '—';
|
||||||
|
return `${v} ${unit}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isZero = (v) => v == null || v === '' || String(v) === '0';
|
||||||
|
|
||||||
|
const overdueDayMetrics = computed(() => {
|
||||||
|
const p = overProduct.value;
|
||||||
|
return [
|
||||||
|
{ label: '当前最大天数(所有)', value: isZero(p.cuAllMaxDay) ? '—' : `${p.cuAllMaxDay} 天`, isZero: isZero(p.cuAllMaxDay) },
|
||||||
|
{ label: '当前最大天数(非循环贷)', value: isZero(p.cuNlMaxDay) ? '—' : `${p.cuNlMaxDay} 天`, isZero: isZero(p.cuNlMaxDay) },
|
||||||
|
{ label: '历史最大天数(所有)', value: isZero(p.hiAllMaxDay) ? '—' : `${p.hiAllMaxDay} 天`, isZero: isZero(p.hiAllMaxDay) },
|
||||||
|
{ label: '当前最大期数(所有)', value: isZero(p.cuAllMaxIssue) ? '—' : `${p.cuAllMaxIssue} 期`, isZero: isZero(p.cuAllMaxIssue) },
|
||||||
|
{ label: '当前最大期数(非循环贷)', value: isZero(p.cuNlMaxIssue) ? '—' : `${p.cuNlMaxIssue} 期`, isZero: isZero(p.cuNlMaxIssue) },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const recentOverdueMetrics = computed(() => {
|
||||||
|
const p = overProduct.value;
|
||||||
|
return [
|
||||||
|
{ label: '近1天', value: p.overdue1d || '—', danger: p.overdue1d === '逾期' },
|
||||||
|
{ label: '近7天', value: p.overdue7d || '—', danger: p.overdue7d === '逾期' },
|
||||||
|
{ label: '近14天', value: p.overdue14d || '—', danger: p.overdue14d === '逾期' },
|
||||||
|
{ label: '近30天', value: p.overdue30d || '—', danger: p.overdue30d === '逾期' },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '../shared.scss';
|
||||||
|
.grid-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.grid-4 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mini-card {
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.mini-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.mini-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.text-danger { color: #d32f2f; }
|
||||||
|
.text-success { color: #008000; }
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
346
src/ui/DWBG5SAM/components/PerLoanDimentSection.vue
Normal file
346
src/ui/DWBG5SAM/components/PerLoanDimentSection.vue
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tyzm-section">
|
||||||
|
<div class="section-title">履约放款产品</div>
|
||||||
|
|
||||||
|
<!-- 海纳履约 - 风险等级 -->
|
||||||
|
<div class="sub-title">
|
||||||
|
海纳履约
|
||||||
|
<span class="tag" :class="riskLevelCls(hnRiskLevel)">{{ riskLevelText(hnRiskLevel) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 放款统计 -->
|
||||||
|
<div class="sub-section-label">放款统计</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>指标</th>
|
||||||
|
<th>近1月</th>
|
||||||
|
<th>近3月</th>
|
||||||
|
<th>近6月</th>
|
||||||
|
<th>近12月</th>
|
||||||
|
<th>近24月</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>放贷次数</td>
|
||||||
|
<td>{{ ls.lendLastMonth ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendCoLast3Months ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendCoLast6Month ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendCoLast12Month ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendCoLast24Month ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>放款机构数</td>
|
||||||
|
<td>{{ ls.lendOrgLastMonth ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendOrgLast3Months ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendOrgLast6Month ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendOrgLast12Month ?? '—' }}</td>
|
||||||
|
<td>{{ ls.lendOrgLast24Month ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>放贷金额</td>
|
||||||
|
<td>{{ ls.lendAmLastMonth || '—' }}</td>
|
||||||
|
<td>{{ ls.lendAmLast3Month || '—' }}</td>
|
||||||
|
<td>{{ ls.lendAmLast6Month || '—' }}</td>
|
||||||
|
<td>{{ ls.lendAmLast12Month || '—' }}</td>
|
||||||
|
<td>{{ ls.lendAmLast24Month || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 放款详情 -->
|
||||||
|
<div class="sub-section-label">放款详情</div>
|
||||||
|
<div class="grid-3">
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">最近放款日期</div>
|
||||||
|
<div class="mini-value">{{ ls.lastLendDate || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">距最近放款天数</div>
|
||||||
|
<div class="mini-value">{{ ls.daysSinceLastLend || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">24月分期类机构</div>
|
||||||
|
<div class="mini-value">{{ ls.installmenCount ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">12月分期类机构</div>
|
||||||
|
<div class="mini-value">{{ ls.installmen12Count ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">24月网络贷款机构</div>
|
||||||
|
<div class="mini-value">{{ ls.onlineLendCount ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">12月网络贷款机构</div>
|
||||||
|
<div class="mini-value">{{ ls.onlineLend12Count ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 近12个月贷款金额分布 -->
|
||||||
|
<div class="sub-section-label">近12个月贷款金额分布</div>
|
||||||
|
<div class="grid-4">
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">1000以下</div>
|
||||||
|
<div class="mini-value">{{ ls.loan12Count1000F ?? '—' }} 笔</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">1000-3000</div>
|
||||||
|
<div class="mini-value">{{ ls.loan12Count3000F ?? '—' }} 笔</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">3000-10000</div>
|
||||||
|
<div class="mini-value">{{ ls.loan12Count10000F ?? '—' }} 笔</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">10000以上</div>
|
||||||
|
<div class="mini-value">{{ ls.loan12Count10000M ?? '—' }} 笔</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 履约情况 -->
|
||||||
|
<div class="sub-section-label">履约情况</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>指标</th>
|
||||||
|
<th>近1月</th>
|
||||||
|
<th>近3月</th>
|
||||||
|
<th>近6月</th>
|
||||||
|
<th>近12月</th>
|
||||||
|
<th>近24月</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>履约次数</td>
|
||||||
|
<td>{{ perf.perLastMonth ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perLast3Months ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perLast6Months ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perLast12Months ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perLast24Months ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>履约金额</td>
|
||||||
|
<td>{{ perf.perAmLastMonth || '—' }}</td>
|
||||||
|
<td>{{ perf.perAmLast3Months || '—' }}</td>
|
||||||
|
<td>{{ perf.perAmLast6Months || '—' }}</td>
|
||||||
|
<td>{{ perf.perAmLast12Months || '—' }}</td>
|
||||||
|
<td>{{ perf.perAmLast24Months || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>还款异常次数</td>
|
||||||
|
<td>{{ perf.perExLastMonth ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perExLast3Months ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perExLast6Months ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perExLast12Months ?? '—' }}</td>
|
||||||
|
<td>{{ perf.perExLast24Months ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="grid-3" style="margin-top:12px">
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">正常还款占比</div>
|
||||||
|
<div class="mini-value">{{ perf.normalRatio || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">信用贷款时长</div>
|
||||||
|
<div class="mini-value">{{ perf.creditLoDuration ? `${perf.creditLoDuration} 天` : '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">已结清订单数</div>
|
||||||
|
<div class="mini-value">{{ perf.settledOrderCount ?? '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mini-card">
|
||||||
|
<div class="mini-label">距最近履约</div>
|
||||||
|
<div class="mini-value">{{ perf.daysPerformance || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 逾期记录 -->
|
||||||
|
<div class="sub-section-label">逾期记录</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>指标</th>
|
||||||
|
<th>近6月</th>
|
||||||
|
<th>近12月</th>
|
||||||
|
<th>近24月</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>M0+ 笔数</td>
|
||||||
|
<td>{{ od.m0PLast6Months ?? '—' }}</td>
|
||||||
|
<td>{{ od.m0PlusCoLast12Month ?? '—' }}</td>
|
||||||
|
<td>{{ od.m0PlusCoLast24Month ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>M1+ 笔数</td>
|
||||||
|
<td>{{ od.m1PLast6Months ?? '—' }}</td>
|
||||||
|
<td>{{ od.m1PlusCoLast12Month ?? '—' }}</td>
|
||||||
|
<td>{{ od.m1PlusCoLast24Month ?? '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>累计金额</td>
|
||||||
|
<td>{{ od.totalAmLast6Month || '—' }}</td>
|
||||||
|
<td>{{ od.totalAmLast12Month || '—' }}</td>
|
||||||
|
<td>{{ od.totalAmLast24Month || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 洞察履约 - 当前授信额度 -->
|
||||||
|
<div class="sub-title">洞察履约</div>
|
||||||
|
<div class="sub-section-label">当前授信额度 / 放款时间间隔</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>授信总额度(所有机构)</th>
|
||||||
|
<td>{{ cld.curAllLpAcct || '—' }}</td>
|
||||||
|
<th>最高授信额度(所有机构)</th>
|
||||||
|
<td>{{ cld.maxAllLpAcct || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>放款时间间隔(所有机构)</th>
|
||||||
|
<td>{{ cld.intvlAll || '—' }} 天</td>
|
||||||
|
<th>放款时间间隔(非循环贷)</th>
|
||||||
|
<td>{{ cld.intvlAllNl || '—' }} 天</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="sub-section-label">授信放款情况</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>指标</th>
|
||||||
|
<th>近90天</th>
|
||||||
|
<th>近180天</th>
|
||||||
|
<th>近365天</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>授信机构数(所有)</td>
|
||||||
|
<td>{{ cld.crAllNum90 || '—' }}</td>
|
||||||
|
<td>{{ cld.crAllNum180 || '—' }}</td>
|
||||||
|
<td>{{ cld.crAllNum365 || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>放款机构数(所有)</td>
|
||||||
|
<td>{{ cld.loOr90Days || '—' }}</td>
|
||||||
|
<td>{{ cld.loOr180Days || '—' }}</td>
|
||||||
|
<td>{{ cld.loOr365Days || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>放款金额(所有)</td>
|
||||||
|
<td>{{ cld.loOrgAm90Days || '—' }}</td>
|
||||||
|
<td>{{ cld.loOrgAm180Days || '—' }}</td>
|
||||||
|
<td>{{ cld.loOrgAm365Days || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- 结清借据 -->
|
||||||
|
<div class="sub-section-label">结清借据情况</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>指标</th>
|
||||||
|
<th>近90天</th>
|
||||||
|
<th>近180天</th>
|
||||||
|
<th>近365天</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>结清借据数(所有机构)</td>
|
||||||
|
<td>{{ rld.rpyAll90dEndNum || '—' }}</td>
|
||||||
|
<td>{{ rld.rpyAll180dEndNum || '—' }}</td>
|
||||||
|
<td>{{ rld.rpyAll365dEndNum || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>结清金额(所有机构)</td>
|
||||||
|
<td>{{ rld.rpyAll90dEndAmt || '—' }}</td>
|
||||||
|
<td>{{ rld.rpyAll180dEndAmt || '—' }}</td>
|
||||||
|
<td>{{ rld.rpyAll365dEndAmt || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>累计还款金额(所有机构)</td>
|
||||||
|
<td>{{ rld.rpyAll90d || '—' }}</td>
|
||||||
|
<td>{{ rld.rpyAll180d || '—' }}</td>
|
||||||
|
<td>{{ rld.rpyAll365d || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div v-if="!hasData" class="empty-hint">暂无履约放款数据</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const hnRiskLevel = computed(() => props.data.hnRiskLevel || '');
|
||||||
|
const ls = computed(() => props.data.lendingStatistics || {});
|
||||||
|
const perf = computed(() => props.data.performanceStatistics || {});
|
||||||
|
const od = computed(() => props.data.overdueRecord || {});
|
||||||
|
const cld = computed(() => props.data.creditLoanDetails || {});
|
||||||
|
const rld = computed(() => props.data.repayLoanDetails || {});
|
||||||
|
const hasData = computed(() => Object.keys(props.data).length > 0);
|
||||||
|
|
||||||
|
const riskLevelCls = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return 'tag-danger';
|
||||||
|
if (lv === '2' || lv === 2) return 'tag-warning';
|
||||||
|
return 'tag-success';
|
||||||
|
};
|
||||||
|
const riskLevelText = (lv) => {
|
||||||
|
if (lv === '1' || lv === 1) return '高风险';
|
||||||
|
if (lv === '2' || lv === 2) return '中风险';
|
||||||
|
return '低风险';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '../shared.scss';
|
||||||
|
.grid-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.grid-4 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mini-card {
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.mini-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.mini-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.sub-section-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #6b7280;
|
||||||
|
margin: 10px 0 8px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
804
src/ui/DWBG5SAM/index.vue
Normal file
804
src/ui/DWBG5SAM/index.vue
Normal file
@@ -0,0 +1,804 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tyzm-report">
|
||||||
|
<div class="report-header">
|
||||||
|
<h1 class="report-title">天远指谜报告</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 基础信息 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">个人基础信息</div>
|
||||||
|
<table class="table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>姓名</th>
|
||||||
|
<td>{{ userInfo.name || '—' }}</td>
|
||||||
|
<th>性别</th>
|
||||||
|
<td>{{ userInfo.sex || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>年龄</th>
|
||||||
|
<td>{{ userInfo.age != null && userInfo.age !== '' ? userInfo.age : '—' }}</td>
|
||||||
|
<th>身份证</th>
|
||||||
|
<td>{{ userInfo.idCard || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>手机号</th>
|
||||||
|
<td>{{ userInfo.phone || '—' }}</td>
|
||||||
|
<th>归属地</th>
|
||||||
|
<td>{{ userInfo.phonePlace || '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="userInfo.location">
|
||||||
|
<th>户籍</th>
|
||||||
|
<td colspan="3">{{ userInfo.location }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>三要素验证</th>
|
||||||
|
<td colspan="3">
|
||||||
|
<span v-if="threeElText.cls" class="tag" :class="threeElText.cls">{{ threeElText.text }}</span>
|
||||||
|
<span v-else>{{ threeElText.text }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>在网状态</th>
|
||||||
|
<td>{{ phoneStatusText }}</td>
|
||||||
|
<th>在网时长</th>
|
||||||
|
<td>{{ phoneOnlineText }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 信用等级 -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">信用等级评估</div>
|
||||||
|
<div class="grid-4">
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">信用评分</div>
|
||||||
|
<div class="metric-value accent">{{ creditLevel.creditScore || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">信用等级</div>
|
||||||
|
<div>
|
||||||
|
<span class="tag" :class="['D', 'E', 'F'].includes((creditLevel.level || '').toUpperCase()) ? 'tag-danger' : 'tag-success'">
|
||||||
|
{{ creditLevelDisplay }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">逾期率</div>
|
||||||
|
<div class="metric-value accent">{{ creditLevel.overdueRate || '—' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="metric">
|
||||||
|
<div class="metric-label">评分/验证风险</div>
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
<span class="tag" :class="overallCreditRisk.cls">{{ overallCreditRisk.text }}</span>
|
||||||
|
<span class="tag" :class="overallOverdueRisk.cls">{{ overallOverdueRisk.text }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户风险名单画像 -->
|
||||||
|
<div class="card card--flush-top">
|
||||||
|
<div class="card-header-red">用户风险名单画像</div>
|
||||||
|
<div class="grid-2">
|
||||||
|
<div class="sub-block">
|
||||||
|
<div class="card-title row-title">
|
||||||
|
<span class="idx-badge">01</span>
|
||||||
|
用户风险画像
|
||||||
|
<span class="tag" :class="portraitRisk.cls">{{ portraitRisk.text }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="hint">该用户命中:{{ portraitHitCount }} 项</p>
|
||||||
|
<div class="risk-portrait-container">
|
||||||
|
<div class="risk-person" aria-hidden="true" />
|
||||||
|
<template v-for="t in ANTI_FRAUD_TAGS" :key="t.code">
|
||||||
|
<div
|
||||||
|
class="risk-tag"
|
||||||
|
:class="[t.tagClass, { hit: antiFraudSet.has(t.code) }]"
|
||||||
|
>
|
||||||
|
{{ t.label }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="risk-tag line"
|
||||||
|
:class="[t.line, { hit: antiFraudSet.has(t.code) }]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sub-block">
|
||||||
|
<div class="card-title row-title">
|
||||||
|
<span class="idx-badge">02</span>
|
||||||
|
风险名单命中
|
||||||
|
<span class="tag" :class="listHitRisk.cls">{{ listHitRisk.text }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="hint">该用户命中:{{ listHitCount }} 项</p>
|
||||||
|
<div class="risk-list-container">
|
||||||
|
<div class="risk-center">风险名单命中</div>
|
||||||
|
<div
|
||||||
|
v-for="item in RISK_LIST_ITEMS"
|
||||||
|
:key="item.key"
|
||||||
|
class="risk-list-tag"
|
||||||
|
:class="[item.tagClass, { hit: isHit(riskListHitObj[item.key]) }]"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 公安不良 -->
|
||||||
|
<div class="card card--flush-top">
|
||||||
|
<div class="card-header-red">公安不良</div>
|
||||||
|
<div class="card-title row-title">
|
||||||
|
<span class="idx-badge">01</span>
|
||||||
|
公安不良命中
|
||||||
|
<span class="tag" :class="policeRisk.cls">{{ policeRisk.text }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="police-bad-container">
|
||||||
|
<div
|
||||||
|
v-for="p in POLICE_ITEMS"
|
||||||
|
:key="p.key"
|
||||||
|
class="police-card"
|
||||||
|
:class="{ unhit: !isHit(publicSecurity[p.key]) }"
|
||||||
|
>
|
||||||
|
<div class="police-card-header">
|
||||||
|
<div class="police-icon" :class="{ unhit: !isHit(publicSecurity[p.key]) }">{{ p.icon }}</div>
|
||||||
|
<div>
|
||||||
|
<div class="police-title">{{ p.title }}</div>
|
||||||
|
<div class="police-status">{{ isHit(publicSecurity[p.key]) ? '命中' : '未命中' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="police-note">{{ p.note }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 逾期风险详情 -->
|
||||||
|
<div class="card">
|
||||||
|
<OverdueRiskSection :data="overdueRisk" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 借贷评估产品 -->
|
||||||
|
<div class="card">
|
||||||
|
<LoanEvaluationSection :data="loanEvaluation" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 履约放款产品 -->
|
||||||
|
<div class="card">
|
||||||
|
<PerLoanDimentSection :data="perLoanDiment" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 租赁行为 -->
|
||||||
|
<div class="card">
|
||||||
|
<LeasingBehaviorSection :data="leasingBehavior" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 司法案件 -->
|
||||||
|
<div class="card">
|
||||||
|
<JudiciaryCaseSection :data="judiciaryCase" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 审核结论 -->
|
||||||
|
<div class="card conclusion">
|
||||||
|
<div class="card-title">风控审核结论</div>
|
||||||
|
<div class="conclusion-content">
|
||||||
|
<p v-for="(line, i) in conclusionLines" :key="i">
|
||||||
|
<template v-if="line.ok === true">✅</template>
|
||||||
|
<template v-else-if="line.ok === false">❌</template>
|
||||||
|
<template v-else>🎯</template>
|
||||||
|
<span v-if="line.strong"><strong class="accent-text">{{ line.text }}</strong></span>
|
||||||
|
<span v-else>{{ line.text }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import {
|
||||||
|
LEVEL_LABEL,
|
||||||
|
ANTI_FRAUD_TAGS,
|
||||||
|
RISK_LIST_ITEMS,
|
||||||
|
POLICE_ITEMS,
|
||||||
|
isHit,
|
||||||
|
riskLevelLabel,
|
||||||
|
} from './riskReportHelper';
|
||||||
|
import OverdueRiskSection from './components/OverdueRiskSection.vue';
|
||||||
|
import LoanEvaluationSection from './components/LoanEvaluationSection.vue';
|
||||||
|
import PerLoanDimentSection from './components/PerLoanDimentSection.vue';
|
||||||
|
import LeasingBehaviorSection from './components/LeasingBehaviorSection.vue';
|
||||||
|
import JudiciaryCaseSection from './components/JudiciaryCaseSection.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
apiId: {
|
||||||
|
type: String,
|
||||||
|
default: 'TEST_API_001',
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
notifyRiskStatus: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根数据处理(兼容嵌套结构)
|
||||||
|
const root = computed(() => {
|
||||||
|
const d = props.data;
|
||||||
|
if (!d || typeof d !== 'object') return {};
|
||||||
|
const inner = d.data;
|
||||||
|
const isReportBody = (o) =>
|
||||||
|
o &&
|
||||||
|
typeof o === 'object' &&
|
||||||
|
('creditLevel' in o || 'userInfo' in o || 'judiciaryCase' in o);
|
||||||
|
if (inner && typeof inner === 'object' && isReportBody(inner) && !isReportBody(d)) {
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 各模块数据提取
|
||||||
|
const userInfo = computed(() => root.value.userInfo || {});
|
||||||
|
const creditLevel = computed(() => root.value.creditLevel || {});
|
||||||
|
const userRiskPortrait = computed(() => root.value.userRiskPortrait || {});
|
||||||
|
const publicSecurity = computed(() => root.value.publicSecurity || {});
|
||||||
|
const overdueRisk = computed(() => root.value.overdueRisk || {});
|
||||||
|
const judiciaryCase = computed(() => root.value.judiciaryCase || {});
|
||||||
|
|
||||||
|
const overdueProduct = computed(() => overdueRisk.value.overdueRiskProduct || {});
|
||||||
|
const probeOverdue = computed(() => overdueRisk.value.probeOverdueRecord || {});
|
||||||
|
const caseOverview = computed(() => judiciaryCase.value.caseOverviewInfo || {});
|
||||||
|
|
||||||
|
const loanEvaluation = computed(() => root.value.loanEvaluation || {});
|
||||||
|
const perLoanDiment = computed(() => root.value.perLoanDiment || {});
|
||||||
|
const leasingBehavior = computed(() => root.value.leasingBehavior || {});
|
||||||
|
|
||||||
|
// 三要素验证文本
|
||||||
|
const threeElText = computed(() => {
|
||||||
|
const s = String(userInfo.value.statusThreeEl ?? '');
|
||||||
|
if (s === '0') return { text: '一致', cls: 'tag-success' };
|
||||||
|
if (s === '1') return { text: '不一致', cls: 'tag-danger' };
|
||||||
|
if (s === '3') return { text: '异常', cls: 'tag-warning' };
|
||||||
|
return { text: '\u2014', cls: '' };
|
||||||
|
});
|
||||||
|
|
||||||
|
// 手机号状态文本
|
||||||
|
const phoneStatusText = computed(() => {
|
||||||
|
const m = {
|
||||||
|
'1': '正常在网',
|
||||||
|
'2': '不在网(空号)',
|
||||||
|
'3': '无短信能力',
|
||||||
|
'4': '欠费',
|
||||||
|
'5': '长时间关机',
|
||||||
|
'6': '关机',
|
||||||
|
'7': '通话中',
|
||||||
|
'-1': '查询失败',
|
||||||
|
};
|
||||||
|
const k = String(userInfo.value.phoneStatus ?? '');
|
||||||
|
return m[k] || (k ? `状态码 ${k}` : '\u2014');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 手机号在网时长
|
||||||
|
const phoneOnlineText = computed(() => {
|
||||||
|
const m = {
|
||||||
|
'0': '[0,3) 月',
|
||||||
|
'3': '[3,6) 月',
|
||||||
|
'6': '[6,12) 月',
|
||||||
|
'12': '[12,24) 月',
|
||||||
|
'24': '24 月及以上',
|
||||||
|
'99': '已离网/新入网/异常',
|
||||||
|
'-1': '查无记录',
|
||||||
|
};
|
||||||
|
const k = String(userInfo.value.phoneOnlie ?? userInfo.value.phoneOnline ?? '');
|
||||||
|
return m[k] || (k ? `${k}(月区间编码)` : '\u2014');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 信用等级展示
|
||||||
|
const creditLevelDisplay = computed(() => {
|
||||||
|
const lv = (creditLevel.value.level || '').toString().toUpperCase().charAt(0);
|
||||||
|
const sub = LEVEL_LABEL[lv] || '';
|
||||||
|
return sub ? `${lv}(${sub})` : lv || '\u2014';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 各类风险等级
|
||||||
|
const overallCreditRisk = computed(() => riskLevelLabel(creditLevel.value.creditScoreRisk));
|
||||||
|
const overallOverdueRisk = computed(() => riskLevelLabel(creditLevel.value.overdueRisk));
|
||||||
|
const portraitRisk = computed(() => riskLevelLabel(userRiskPortrait.value.anRiskLevel));
|
||||||
|
const listHitRisk = computed(() => riskLevelLabel(userRiskPortrait.value.riRiskLevel));
|
||||||
|
const policeRisk = computed(() => riskLevelLabel(publicSecurity.value.riskLevel));
|
||||||
|
|
||||||
|
// 反欺诈命中集合
|
||||||
|
const antiFraudSet = computed(() => {
|
||||||
|
const arr = userRiskPortrait.value.antiFraud;
|
||||||
|
if (!Array.isArray(arr)) return new Set();
|
||||||
|
return new Set(arr.map(String));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 反欺诈命中数量
|
||||||
|
const portraitHitCount = computed(() => ANTI_FRAUD_TAGS.filter((t) => antiFraudSet.value.has(t.code)).length);
|
||||||
|
|
||||||
|
// 风险名单命中对象
|
||||||
|
const riskListHitObj = computed(() => userRiskPortrait.value.riskListHit || {});
|
||||||
|
|
||||||
|
// 风险名单命中数量
|
||||||
|
const listHitCount = computed(() => RISK_LIST_ITEMS.filter((item) => isHit(riskListHitObj.value[item.key])).length);
|
||||||
|
|
||||||
|
// 未结清逾期文本
|
||||||
|
const unsetDueText = computed(() => overdueProduct.value.hasUnsetDue || '\u2014');
|
||||||
|
|
||||||
|
// 审核结论
|
||||||
|
const conclusionLines = computed(() => {
|
||||||
|
const lines = [];
|
||||||
|
const te = threeElText.value;
|
||||||
|
lines.push({
|
||||||
|
ok: te.cls === 'tag-success',
|
||||||
|
text: `身份验证:三要素${te.text === '\u2014' ? '未返回' : te.text}${userInfo.value.phoneStatus === '1' ? ',手机号正常在网' : ''}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const lv = (creditLevel.value.level || '').toString().toUpperCase();
|
||||||
|
const badLevel = ['D', 'E', 'F'].includes(lv);
|
||||||
|
lines.push({
|
||||||
|
ok: !badLevel,
|
||||||
|
text: `信用等级:${creditLevelDisplay.value},逾期率 ${creditLevel.value.overdueRate || '\u2014'}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const bg = Number(caseOverview.value.beigaoTotalCasesCounts) || 0;
|
||||||
|
const ex = Number(caseOverview.value.executionCaseCounts) || 0;
|
||||||
|
lines.push({
|
||||||
|
ok: bg === 0 && ex === 0,
|
||||||
|
text: `司法风险:被告案件 ${bg} 件,执行案件 ${ex} 件`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const overdueBad = overdueRisk.value.overRiskLevel === '1' || unsetDueText.value === '逾期';
|
||||||
|
lines.push({
|
||||||
|
ok: !overdueBad,
|
||||||
|
text: `逾期风险:当前状态 ${unsetDueText.value},最近逾期 ${probeOverdue.value.latestOverdueTime || '\u2014'}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hits = [];
|
||||||
|
if (portraitHitCount.value) hits.push('风险画像命中');
|
||||||
|
if (listHitCount.value) hits.push('风险名单命中');
|
||||||
|
lines.push({
|
||||||
|
ok: portraitHitCount.value === 0 && listHitCount.value === 0,
|
||||||
|
text: `名单与画像:${hits.length ? hits.join('、') : '无命中'}(画像 ${portraitHitCount.value} 项,名单 ${listHitCount.value} 项)`,
|
||||||
|
});
|
||||||
|
|
||||||
|
let suggest = '建议结合业务规则综合判断';
|
||||||
|
if (
|
||||||
|
creditLevel.value.creditScoreRisk === '1' ||
|
||||||
|
userRiskPortrait.value.riRiskLevel === '1' ||
|
||||||
|
publicSecurity.value.riskLevel === '1'
|
||||||
|
) {
|
||||||
|
suggest = '高风险客户,建议谨慎授信或拒绝';
|
||||||
|
} else if (creditLevel.value.creditScoreRisk === '2' || overdueRisk.value.overRiskLevel === '1') {
|
||||||
|
suggest = '存在明显风险信号,建议加强审核';
|
||||||
|
} else {
|
||||||
|
suggest = '整体风险可控,可按常规流程审核';
|
||||||
|
}
|
||||||
|
lines.push({ ok: null, text: `审核建议:${suggest}`, strong: true });
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 风险分数计算(基于信用等级 A-F 五档)
|
||||||
|
const LEVEL_SCORE_MAP = { A: 95, B: 80, C: 65, D: 45, E: 25, F: 10 };
|
||||||
|
const riskScore = computed(() => {
|
||||||
|
const lv = (creditLevel.value.level || '').toString().toUpperCase().charAt(0);
|
||||||
|
const base = LEVEL_SCORE_MAP[lv];
|
||||||
|
if (base != null) return base;
|
||||||
|
// 无信用等级时,根据综合风险因子兜底
|
||||||
|
let s = 70;
|
||||||
|
const penalize = (cond, amount) => { if (cond) s -= amount; };
|
||||||
|
penalize(creditLevel.value.creditScoreRisk === '1', 20);
|
||||||
|
penalize(creditLevel.value.overdueRisk === '1', 12);
|
||||||
|
penalize(userRiskPortrait.value.riRiskLevel === '1', 14);
|
||||||
|
penalize(publicSecurity.value.riskLevel === '1', 16);
|
||||||
|
penalize(overdueRisk.value.overRiskLevel === '1', 12);
|
||||||
|
return Math.max(8, Math.min(100, Math.round(s)));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 透传风险分数给 BaseReport
|
||||||
|
watch(riskScore, (v) => {
|
||||||
|
if (props.apiId && props.notifyRiskStatus) {
|
||||||
|
props.notifyRiskStatus(props.apiId, props.index, v);
|
||||||
|
}
|
||||||
|
}, { immediate: false });
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.apiId && props.notifyRiskStatus) {
|
||||||
|
props.notifyRiskStatus(props.apiId, props.index, riskScore.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 暴露风险分数
|
||||||
|
defineExpose({ riskScore });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.tyzm-report {
|
||||||
|
--tyzm-bg: #f5f7fa;
|
||||||
|
--tyzm-card: #fff;
|
||||||
|
--tyzm-text: #333;
|
||||||
|
--tyzm-muted: #6b7280;
|
||||||
|
--tyzm-border: #e5e7eb;
|
||||||
|
--tyzm-danger: #f43f5e;
|
||||||
|
--tyzm-warn: #d89614;
|
||||||
|
--tyzm-success: #008000;
|
||||||
|
--tyzm-head: #1f2937;
|
||||||
|
font-family: 'Microsoft YaHei', system-ui, sans-serif;
|
||||||
|
color: var(--tyzm-text);
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--tyzm-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-title {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--tyzm-head);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: var(--tyzm-card);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card--flush-top {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header-red {
|
||||||
|
background: linear-gradient(90deg, #f43f5e, #fb923c);
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
margin: 0 -16px 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--tyzm-head);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-title {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.idx-badge {
|
||||||
|
background: var(--tyzm-danger);
|
||||||
|
color: #fff;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--tyzm-muted);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-4 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-wrap {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-1 {
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.grid-4 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
.grid-2 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--tyzm-muted);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value.accent {
|
||||||
|
color: var(--tyzm-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-success {
|
||||||
|
background: #e6f7e6;
|
||||||
|
color: var(--tyzm-success);
|
||||||
|
}
|
||||||
|
.tag-warning {
|
||||||
|
background: #fff7e6;
|
||||||
|
color: var(--tyzm-warn);
|
||||||
|
}
|
||||||
|
.tag-danger {
|
||||||
|
background: #ffe6e6;
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid var(--tyzm-border);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f9fafb;
|
||||||
|
width: 22%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.conclusion {
|
||||||
|
background: #fff8f8;
|
||||||
|
border-left: 4px solid var(--tyzm-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.conclusion-content {
|
||||||
|
line-height: 1.75;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accent-text {
|
||||||
|
color: var(--tyzm-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 风险画像 */
|
||||||
|
.risk-portrait-container {
|
||||||
|
position: relative;
|
||||||
|
min-height: 260px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-person {
|
||||||
|
width: 80px;
|
||||||
|
height: 160px;
|
||||||
|
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 200'%3E%3Crect width='100' height='200' fill='white'/%3E%3Ccircle cx='50' cy='30' r='20' fill='%232563eb'/%3E%3Crect x='35' y='55' width='30' height='80' rx='5' fill='%232563eb'/%3E%3Crect x='30' y='60' width='15' height='60' rx='3' fill='%232563eb'/%3E%3Crect x='55' y='60' width='15' height='60' rx='3' fill='%232563eb'/%3E%3Crect x='38' y='135' width='10' height='50' rx='3' fill='%232563eb'/%3E%3Crect x='52' y='135' width='10' height='50' rx='3' fill='%232563eb'/%3E%3C/svg%3E")
|
||||||
|
center / contain no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-tag {
|
||||||
|
position: absolute;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #475569;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
white-space: nowrap;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-tag.hit {
|
||||||
|
background: var(--tyzm-danger);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-tag.line {
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0;
|
||||||
|
width: 56px;
|
||||||
|
height: 2px;
|
||||||
|
min-height: 0;
|
||||||
|
background: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-tag.line.hit {
|
||||||
|
background: var(--tyzm-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-1 { top: 12px; left: 4px; }
|
||||||
|
.tag-2 { top: 72px; left: 4px; }
|
||||||
|
.tag-3 { top: 132px; left: 4px; }
|
||||||
|
.tag-4 { top: 192px; left: 4px; }
|
||||||
|
.tag-5 { top: 12px; right: 4px; }
|
||||||
|
.tag-6 { top: 72px; right: 4px; }
|
||||||
|
.tag-7 { top: 132px; right: 4px; }
|
||||||
|
.tag-8 { top: 192px; right: 4px; }
|
||||||
|
|
||||||
|
.line-1 { top: 28px; left: 88px; }
|
||||||
|
.line-2 { top: 88px; left: 88px; }
|
||||||
|
.line-3 { top: 148px; left: 88px; }
|
||||||
|
.line-4 { top: 208px; left: 88px; }
|
||||||
|
.line-5 { top: 28px; right: 88px; }
|
||||||
|
.line-6 { top: 88px; right: 88px; }
|
||||||
|
.line-7 { top: 148px; right: 88px; }
|
||||||
|
.line-8 { top: 208px; right: 88px; }
|
||||||
|
|
||||||
|
/* 风险名单 */
|
||||||
|
.risk-list-container {
|
||||||
|
position: relative;
|
||||||
|
min-height: 300px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-center {
|
||||||
|
width: 112px;
|
||||||
|
height: 112px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: radial-gradient(circle, #e0f2fe, #bae6fd);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #0369a1;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.3;
|
||||||
|
padding: 8px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-list-tag {
|
||||||
|
position: absolute;
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: #475569;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
z-index: 2;
|
||||||
|
max-width: 38%;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.25;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-list-tag.hit {
|
||||||
|
background: var(--tyzm-danger);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-tag-1 { top: 0; left: 50%; transform: translateX(-50%); }
|
||||||
|
.list-tag-2 { top: 44px; right: 4px; }
|
||||||
|
.list-tag-3 { top: 110px; right: 4px; }
|
||||||
|
.list-tag-4 { bottom: 0; left: 50%; transform: translateX(-50%); }
|
||||||
|
.list-tag-5 { bottom: 44px; left: 4px; }
|
||||||
|
.list-tag-6 { top: 178px; left: 4px; }
|
||||||
|
.list-tag-7 { top: 44px; left: 4px; }
|
||||||
|
.list-tag-extra { bottom: 110px; right: 4px; }
|
||||||
|
|
||||||
|
/* 公安卡片 */
|
||||||
|
.police-bad-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.police-bad-container {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
border-left: 3px solid var(--tyzm-danger);
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-card.unhit {
|
||||||
|
border-left-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-icon {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
background: var(--tyzm-danger);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-icon.unhit {
|
||||||
|
background: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.police-note {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-top: 6px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
158
src/ui/DWBG5SAM/riskReportHelper.js
Normal file
158
src/ui/DWBG5SAM/riskReportHelper.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
* 风控报告工具函数
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 信用等级映射
|
||||||
|
export const LEVEL_LABEL = {
|
||||||
|
A: '极好',
|
||||||
|
B: '优质',
|
||||||
|
C: '良好',
|
||||||
|
D: '一般',
|
||||||
|
E: '较差',
|
||||||
|
F: '极差',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 反欺诈标签配置
|
||||||
|
export const ANTI_FRAUD_TAGS = [
|
||||||
|
{ code: '21001', label: '疑似恶意借贷', side: 'left', line: 'line-1', tagClass: 'tag-1' },
|
||||||
|
{ code: '11002', label: '疑似网络投机', side: 'left', line: 'line-2', tagClass: 'tag-2' },
|
||||||
|
{ code: '11003', label: '疑似营销欺诈', side: 'left', line: 'line-3', tagClass: 'tag-3' },
|
||||||
|
{ code: '11005', label: '疑似恶意套现', side: 'left', line: 'line-4', tagClass: 'tag-4' },
|
||||||
|
{ code: '21002', label: '疑似职业撸口子', side: 'right', line: 'line-5', tagClass: 'tag-5' },
|
||||||
|
{ code: '11001', label: '疑似涉黑涉赌', side: 'right', line: 'line-6', tagClass: 'tag-6' },
|
||||||
|
{ code: '11004', label: '疑似黑中介包装', side: 'right', line: 'line-7', tagClass: 'tag-7' },
|
||||||
|
{ code: '12002', label: '疑似黑产账号', side: 'right', line: 'line-8', tagClass: 'tag-8' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 风险名单配置
|
||||||
|
export const RISK_LIST_ITEMS = [
|
||||||
|
{ key: 'bankOverdueRecord', label: '银行逾期记录', tagClass: 'list-tag-1' },
|
||||||
|
{ key: 'overdueRecords', label: '百行逾期记录', tagClass: 'list-tag-2' },
|
||||||
|
{ key: 'disappearance', label: '疑似恶意失联', tagClass: 'list-tag-3' },
|
||||||
|
{ key: 'creditAndHighConstraint', label: '失信限高被执行人', tagClass: 'list-tag-4' },
|
||||||
|
{ key: 'groupFraudList', label: '团伙欺诈名单', tagClass: 'list-tag-5' },
|
||||||
|
{ key: 'judicialRecord', label: '司法涉诉记录', tagClass: 'list-tag-6' },
|
||||||
|
{ key: 'creditOverdueRecord', label: '信贷逾期记录', tagClass: 'list-tag-7' },
|
||||||
|
{ key: 'vehicleLeaseViolation', label: '车辆租赁违约', tagClass: 'list-tag-extra' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 公安不良配置
|
||||||
|
export const POLICE_ITEMS = [
|
||||||
|
{ key: 'front', title: '前科', icon: '🔒', note: '注:在逃、盗窃、诈骗、抢劫、故意伤害、强奸、在刑或前科等' },
|
||||||
|
{ key: 'economyFront', title: '经济类前科', icon: '💳', note: '注:破坏金融秩序、非法吸存、连发贷款、金融诈骗、在刑或前科等' },
|
||||||
|
{ key: 'disrupSocial', title: '妨害社会管理秩序', icon: '🌐', note: '注:扰乱社会公共秩序、妨害司法、涉毒、涉黄、在刑或前科等' },
|
||||||
|
{ key: 'trafficRelated', title: '涉交通案件', icon: '🚗', note: '注:危险驾驶、交通肇事等' },
|
||||||
|
{ key: 'ikey', title: '重点', icon: '🔍', note: '注:危害国家、公共安全,涉恐、涉爆、涉稳、涉黑、涉及境外等' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否命中(1/true/'1' 为命中)
|
||||||
|
* @param {any} v
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isHit(v) {
|
||||||
|
return v === 1 || v === '1' || v === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险等级标签转换
|
||||||
|
* @param {string|number} level
|
||||||
|
* @returns { { text: string, cls: string } }
|
||||||
|
*/
|
||||||
|
export function riskLevelLabel(level) {
|
||||||
|
if (level === '1' || level === 1) return { text: '高风险', cls: 'tag-danger' };
|
||||||
|
if (level === '2' || level === 2) return { text: '中风险', cls: 'tag-warning' };
|
||||||
|
return { text: '低风险', cls: 'tag-success' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化单元格文本(空值转—,对象转JSON)
|
||||||
|
* @param {any} v
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function cellText(v) {
|
||||||
|
if (v === null || v === undefined || v === '') return '—';
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
try {
|
||||||
|
const s = JSON.stringify(v);
|
||||||
|
return s.length > 400 ? `${s.slice(0, 400)}…` : s;
|
||||||
|
} catch {
|
||||||
|
return String(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度展平对象为路径-值列表
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} prefix
|
||||||
|
* @returns {Array<{ key: string, value: string }>}
|
||||||
|
*/
|
||||||
|
export function deepFlattenRows(obj, prefix = '') {
|
||||||
|
const rows = [];
|
||||||
|
if (obj === null || obj === undefined) {
|
||||||
|
if (prefix) rows.push({ key: prefix, value: '—' });
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
if (typeof obj !== 'object') {
|
||||||
|
rows.push({ key: prefix || '值', value: cellText(obj) });
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
rows.push({ key: prefix || '[]', value: cellText(obj) });
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
const keys = Object.keys(obj).sort();
|
||||||
|
if (keys.length === 0) {
|
||||||
|
rows.push({ key: prefix || '(空对象)', value: '{}' });
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
for (const k of keys) {
|
||||||
|
const v = obj[k];
|
||||||
|
const path = prefix ? `${prefix}.${k}` : k;
|
||||||
|
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
||||||
|
rows.push(...deepFlattenRows(v, path));
|
||||||
|
} else {
|
||||||
|
rows.push({ key: path, value: cellText(v) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取顶级分段
|
||||||
|
* @param {object} obj
|
||||||
|
* @returns {Array<{ heading: string, rows: Array }>}
|
||||||
|
*/
|
||||||
|
export function topLevelSections(obj) {
|
||||||
|
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return [];
|
||||||
|
return Object.keys(obj)
|
||||||
|
.sort()
|
||||||
|
.map((heading) => {
|
||||||
|
const val = obj[heading];
|
||||||
|
if (val !== null && typeof val === 'object' && !Array.isArray(val)) {
|
||||||
|
return { heading, rows: deepFlattenRows(val) };
|
||||||
|
}
|
||||||
|
return { heading, rows: [{ key: heading, value: cellText(val) }] };
|
||||||
|
})
|
||||||
|
.filter((s) => s.rows.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟风险通知钩子(简化版)
|
||||||
|
* @param {object} props
|
||||||
|
* @param {import('vue').ComputedRef<number>} riskScore
|
||||||
|
*/
|
||||||
|
export function useRiskNotifier(props, riskScore) {
|
||||||
|
// 模拟风险通知逻辑
|
||||||
|
if (riskScore.value < 60) {
|
||||||
|
console.log(`[风险通知] 接口ID:${props.apiId} 索引:${props.index} 风险分数:${riskScore.value} 高风险`);
|
||||||
|
props.notifyRiskStatus?.({
|
||||||
|
apiId: props.apiId,
|
||||||
|
index: props.index,
|
||||||
|
riskScore: riskScore.value,
|
||||||
|
riskLevel: 'high'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
137
src/ui/DWBG5SAM/shared.scss
Normal file
137
src/ui/DWBG5SAM/shared.scss
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// 共享样式变量和基础类
|
||||||
|
.tyzm-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin: 14px 0 10px;
|
||||||
|
padding-left: 8px;
|
||||||
|
border-left: 3px solid #f43f5e;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f9fafb;
|
||||||
|
width: 22%;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-success {
|
||||||
|
background: #e6f7e6;
|
||||||
|
color: #008000;
|
||||||
|
}
|
||||||
|
.tag-warning {
|
||||||
|
background: #fff7e6;
|
||||||
|
color: #d89614;
|
||||||
|
}
|
||||||
|
.tag-danger {
|
||||||
|
background: #ffe6e6;
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-2 { margin-left: 8px; }
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
text-align: center;
|
||||||
|
color: #9ca3af;
|
||||||
|
padding: 20px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-list {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-item {
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-left: 3px solid #6366f1;
|
||||||
|
|
||||||
|
&.danger {
|
||||||
|
border-left-color: #f43f5e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-number {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-body {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 1.6;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-label {
|
||||||
|
color: #6b7280;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: #6366f1;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
&:hover { text-decoration: underline; }
|
||||||
|
}
|
||||||
34
src/ui/IVYZ4Y27/abilityCompetitive.json
Normal file
34
src/ui/IVYZ4Y27/abilityCompetitive.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"value": "A",
|
||||||
|
"label": "985院校"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "B",
|
||||||
|
"label": "双一流"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "C",
|
||||||
|
"label": "211院校"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "D",
|
||||||
|
"label": "一本院校"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "E",
|
||||||
|
"label": "二本院校"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "F",
|
||||||
|
"label": "大专院校"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "G",
|
||||||
|
"label": "成人本科"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "H",
|
||||||
|
"label": "其他"
|
||||||
|
}
|
||||||
|
]
|
||||||
22
src/ui/IVYZ4Y27/abilityCompetitiveDegree.json
Normal file
22
src/ui/IVYZ4Y27/abilityCompetitiveDegree.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"value": "9",
|
||||||
|
"label": "博士研究生"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "8",
|
||||||
|
"label": "硕士研究生"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "7",
|
||||||
|
"label": "本科"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "6",
|
||||||
|
"label": "大专"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "5",
|
||||||
|
"label": "其他"
|
||||||
|
}
|
||||||
|
]
|
||||||
7682
src/ui/IVYZ4Y27/abilityField.json
Normal file
7682
src/ui/IVYZ4Y27/abilityField.json
Normal file
File diff suppressed because it is too large
Load Diff
12478
src/ui/IVYZ4Y27/abilityName.json
Normal file
12478
src/ui/IVYZ4Y27/abilityName.json
Normal file
File diff suppressed because it is too large
Load Diff
282
src/ui/IVYZ4Y27/index.vue
Normal file
282
src/ui/IVYZ4Y27/index.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed, onMounted } from 'vue';
|
||||||
|
import { useRiskNotifier } from '@/composables/useRiskNotifier';
|
||||||
|
import abilityCompetitive from './abilityCompetitive.json';
|
||||||
|
import abilityCompetitiveDegree from './abilityCompetitiveDegree.json';
|
||||||
|
import abilityName from './abilityName.json';
|
||||||
|
import abilityField from './abilityField.json';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
apiId: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
notifyRiskStatus: {
|
||||||
|
type: Function,
|
||||||
|
default: () => { },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导入图片图标
|
||||||
|
import rkpmIcon from '@/assets/images/report/rkpm.png';
|
||||||
|
import zymcIcon from '@/assets/images/report/zymc.png';
|
||||||
|
import xxxsIcon from '@/assets/images/report/xxxs.png';
|
||||||
|
import xxlxIcon from '@/assets/images/report/xxlx.png';
|
||||||
|
import bysjIcon from '@/assets/images/report/bysj.png';
|
||||||
|
import xlIcon from '@/assets/images/report/xl.png';
|
||||||
|
|
||||||
|
// 计算风险评分
|
||||||
|
const riskScore = computed(() => {
|
||||||
|
return 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用 composable 通知父组件风险评分
|
||||||
|
useRiskNotifier(props, riskScore);
|
||||||
|
|
||||||
|
// 暴露给父组件
|
||||||
|
defineExpose({
|
||||||
|
riskScore
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取列表数据
|
||||||
|
const abilityList = computed(() => {
|
||||||
|
return props.data?.abilityInfo || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// 字典映射
|
||||||
|
const competitiveMap = {};
|
||||||
|
abilityCompetitive.forEach(item => {
|
||||||
|
competitiveMap[item.value] = item.label;
|
||||||
|
});
|
||||||
|
|
||||||
|
const competitiveDegreeMap = {};
|
||||||
|
abilityCompetitiveDegree.forEach(item => {
|
||||||
|
competitiveDegreeMap[item.value] = item.label;
|
||||||
|
});
|
||||||
|
|
||||||
|
const nameMap = {};
|
||||||
|
abilityName.forEach(item => {
|
||||||
|
nameMap[item.value] = item.label;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldMap = {};
|
||||||
|
abilityField.forEach(item => {
|
||||||
|
fieldMap[item.value] = item.label;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 翻译函数
|
||||||
|
const getCompetitiveLabel = (val) => competitiveMap[val] || val || '未知';
|
||||||
|
const getCompetitiveDegreeLabel = (val) => competitiveDegreeMap[val] || val || '未知';
|
||||||
|
const getNameLabel = (val) => nameMap[val] || val || '未知';
|
||||||
|
|
||||||
|
// 专业方向翻译:C99999/B99999 等以 99999 结尾的表示未匹配到相关专业
|
||||||
|
const getFieldLabel = (val) => {
|
||||||
|
if (!val) return '未知';
|
||||||
|
if (val.endsWith('99999')) {
|
||||||
|
const prefix = val.charAt(0);
|
||||||
|
const prefixMap = { 'A': '硕士研究生/博士研究生', 'B': '本科', 'C': '专科' };
|
||||||
|
return `${prefixMap[prefix] || ''}(未匹配到相关专业)`;
|
||||||
|
}
|
||||||
|
return fieldMap[val] || val || '未知';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化日期 yyyy-MM-dd -> yyyy年MM月dd日
|
||||||
|
const formatDate = (dateStr) => {
|
||||||
|
if (!dateStr) return '未知';
|
||||||
|
const parts = dateStr.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
return `${parts[0]}年${parts[1]}月${parts[2]}日`;
|
||||||
|
}
|
||||||
|
if (parts.length === 2) {
|
||||||
|
return `${parts[0]}年${parts[1]}月`;
|
||||||
|
}
|
||||||
|
return dateStr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取核心竞争力等级对应的颜色样式
|
||||||
|
const getCompetitiveBadgeClass = (val) => {
|
||||||
|
const classMap = {
|
||||||
|
'A': 'bg-amber-500 text-white',
|
||||||
|
'B': 'bg-green-500 text-white',
|
||||||
|
'C': 'bg-blue-500 text-white',
|
||||||
|
'D': 'bg-indigo-500 text-white',
|
||||||
|
'E': 'bg-cyan-500 text-white',
|
||||||
|
'F': 'bg-gray-500 text-white',
|
||||||
|
'G': 'bg-orange-500 text-white',
|
||||||
|
'H': 'bg-gray-400 text-white',
|
||||||
|
};
|
||||||
|
return classMap[val] || 'bg-gray-400 text-white';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取学历等级对应的颜色样式
|
||||||
|
const getDegreeBadgeClass = (val) => {
|
||||||
|
const classMap = {
|
||||||
|
'9': 'bg-purple-500 text-white',
|
||||||
|
'8': 'bg-blue-600 text-white',
|
||||||
|
'7': 'bg-blue-500 text-white',
|
||||||
|
'6': 'bg-cyan-600 text-white',
|
||||||
|
'5': 'bg-gray-500 text-white',
|
||||||
|
};
|
||||||
|
return classMap[val] || 'bg-gray-400 text-white';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否有数据
|
||||||
|
const hasData = computed(() => {
|
||||||
|
return abilityList.value.length > 0;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="hasData" class="card max-w-4xl mx-auto">
|
||||||
|
<!-- 头部区域 -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-12 h-12 flex items-center justify-center">
|
||||||
|
<img :src="xlIcon" alt="学历图标" class="w-12 h-12" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900">学历信息</h2>
|
||||||
|
<p class="text-sm text-gray-500">Education Information</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
共 {{ abilityList.length }} 条记录
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学历列表 -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div v-for="(item, idx) in abilityList" :key="idx"
|
||||||
|
class="bg-white border border-gray-200 rounded-xl overflow-hidden hover:shadow-md transition-shadow">
|
||||||
|
|
||||||
|
<!-- 顶部:学历等级与核心竞争力 -->
|
||||||
|
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 px-5 py-4 border-b border-blue-100">
|
||||||
|
<div class="flex items-center justify-between flex-wrap gap-2">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-10 h-10 rounded-full bg-blue-500 flex items-center justify-center">
|
||||||
|
<span class="text-white text-lg font-bold">{{ idx + 1 }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
|
<span v-if="item.abilityCompetitiveDegree"
|
||||||
|
:class="['inline-flex items-center px-3 py-1 rounded-full text-sm font-medium', getDegreeBadgeClass(item.abilityCompetitiveDegree)]">
|
||||||
|
{{ getCompetitiveDegreeLabel(item.abilityCompetitiveDegree) }}
|
||||||
|
</span>
|
||||||
|
<span v-if="item.abilityCompetitive"
|
||||||
|
:class="['inline-flex items-center px-3 py-1 rounded-full text-xs font-medium', getCompetitiveBadgeClass(item.abilityCompetitive)]">
|
||||||
|
{{ getCompetitiveLabel(item.abilityCompetitive) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详细信息 -->
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<!-- 院校名称 -->
|
||||||
|
<div v-if="item.abilityName"
|
||||||
|
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<img :src="xxlxIcon" alt="院校名称" class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm text-gray-600 mb-1">院校名称</div>
|
||||||
|
<div class="text-base font-medium text-gray-900">{{ getNameLabel(item.abilityName)
|
||||||
|
}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学习类型 -->
|
||||||
|
<div v-if="item.abilityType"
|
||||||
|
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<img :src="xxxsIcon" alt="学习类型" class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm text-gray-600 mb-1">学习类型</div>
|
||||||
|
<div class="text-base font-medium text-gray-900">{{ item.abilityType }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 专业方向 -->
|
||||||
|
<div v-if="item.abilityField"
|
||||||
|
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<img :src="zymcIcon" alt="专业方向" class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm text-gray-600 mb-1">专业方向</div>
|
||||||
|
<div class="text-base font-medium text-gray-900">{{ getFieldLabel(item.abilityField)
|
||||||
|
}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 学习时间 -->
|
||||||
|
<div v-if="item.abilityStartDate || item.abilityEndDate"
|
||||||
|
class="bg-gray-50 rounded-lg p-4 border border-gray-200 hover:border-blue-300 transition-colors">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<img :src="bysjIcon" alt="学习时间" class="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm text-gray-600 mb-1">学习时间</div>
|
||||||
|
<div class="text-base font-medium text-gray-900">
|
||||||
|
{{ formatDate(item.abilityStartDate) }} ~ {{ formatDate(item.abilityEndDate) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 无数据状态 -->
|
||||||
|
<div v-else class="card max-w-2xl mx-auto">
|
||||||
|
<div class="flex flex-col items-center py-12 text-center">
|
||||||
|
<div class="w-20 h-20 flex items-center justify-center mb-4">
|
||||||
|
<img :src="xlIcon" alt="学历图标" class="w-20 h-20 opacity-40" />
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 mb-2">暂无学历信息</h3>
|
||||||
|
<p class="text-sm text-gray-500 max-w-md">
|
||||||
|
系统中暂无相关的学历信息记录。这可能是因为学历信息未公开或数据正在同步中。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.card {
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0px 0px 24px 0px #3F3F3F0F;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-50 {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-gray-50:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user