add
This commit is contained in:
1072
public/DWBG9FB3.json
Normal file
1072
public/DWBG9FB3.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -590,6 +590,11 @@ const featureMap = {
|
|||||||
component: defineAsyncComponent(() => import("@/ui/DWBG5SAM/index.vue")),
|
component: defineAsyncComponent(() => import("@/ui/DWBG5SAM/index.vue")),
|
||||||
remark: '天远指谜报告综合展示身份核验、信用等级、风险画像与名单、公安不良、逾期与司法案件等维度,数据来源于合作机构,仅供参考。',
|
remark: '天远指谜报告综合展示身份核验、信用等级、风险画像与名单、公安不良、逾期与司法案件等维度,数据来源于合作机构,仅供参考。',
|
||||||
},
|
},
|
||||||
|
DWBG9FB3: {
|
||||||
|
name: "个人大数据风险档案",
|
||||||
|
component: defineAsyncComponent(() => import("@/ui/DWBG9FB3/index.vue")),
|
||||||
|
remark: '个人大数据风险档案综合展示风险评估、基本信息、借贷画像、逾期黑名单、欺诈黑名单、投诉风险、逾期勘测、借贷意向与司法案件等维度,数据来源于合作机构,仅供参考。',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -685,6 +690,7 @@ const featureRiskLevels = {
|
|||||||
'CFLX3A9B': 5, // 法院被执行人限高版
|
'CFLX3A9B': 5, // 法院被执行人限高版
|
||||||
'IVYZ4Y27' :3 , //xueli
|
'IVYZ4Y27' :3 , //xueli
|
||||||
'DWBG5SAM': 10,
|
'DWBG5SAM': 10,
|
||||||
|
'DWBG9FB3': 10,
|
||||||
|
|
||||||
// 🟡 中风险类 - 权重 5
|
// 🟡 中风险类 - 权重 5
|
||||||
'QYGL3F8E': 5, // 人企关系加强版
|
'QYGL3F8E': 5, // 人企关系加强版
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ const router = createRouter({
|
|||||||
return import("./views/Report.vue");
|
return import("./views/Report.vue");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/DWBG9FB3",
|
||||||
|
name: "DWBG9FB3",
|
||||||
|
component: () => import("./views/DWBG9FB3Report.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
96
src/ui/DWBG9FB3/components/BasicInfoSection.vue
Normal file
96
src/ui/DWBG9FB3/components/BasicInfoSection.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-grid-3">
|
||||||
|
<div class="gamma-card info-card">
|
||||||
|
<div class="gamma-title"><span>👤</span> 用户信息</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">姓名:</span>
|
||||||
|
<span class="info-value">
|
||||||
|
{{ maskedName }}
|
||||||
|
<span v-if="realNameAuth.coincide" class="gamma-tag">身份证姓名一致</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item"><span class="info-label">性别</span><span>{{ params.sex || '—' }}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">年龄</span><span>{{ params.age || '—' }}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">身份证号</span><span>{{ maskedIdCard }}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">户籍地</span><span>{{ params.location || '—' }}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gamma-card info-card">
|
||||||
|
<div class="gamma-title"><span>📱</span> 手机信息</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">手机号:</span>
|
||||||
|
<span>
|
||||||
|
{{ maskedMobile }}
|
||||||
|
<span v-if="mobile3Verify.status === 1" class="gamma-tag">身份证姓名手机号一致</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">手机号在网时长:</span>
|
||||||
|
<span>{{ durationText }} ✅</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">手机号在网状态:</span>
|
||||||
|
<span :class="mobile4Verify.status === 2 ? 'gamma-text-danger' : ''">
|
||||||
|
{{ mobileStatusText(mobile4Verify.status) }}
|
||||||
|
<span v-if="mobile4Verify.status === 2"> ❗</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item"><span class="info-label">手机号运营商:</span><span>{{ params.carrier || '—' }}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">手机号归属地:</span><span>{{ params.phonePlace || '—' }}</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gamma-card info-card">
|
||||||
|
<div class="gamma-title"><span>⚖️</span> 司法风险</div>
|
||||||
|
<div v-for="item in courtItems" :key="item.key" class="info-item">
|
||||||
|
<span class="info-label">
|
||||||
|
<span :class="item.hit ? 'gamma-text-danger' : 'gamma-text-success'">{{ item.hit ? '❗' : '✅' }}</span>
|
||||||
|
{{ item.label }}
|
||||||
|
</span>
|
||||||
|
<span :class="item.hit ? 'gamma-text-danger' : ''">{{ item.hit ? '是' : '否' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { maskName, maskIdCard, maskMobile, mobileStatusText, buildCourtRiskItems } from '../reportHelper';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
params: { type: Object, default: () => ({}) },
|
||||||
|
realNameAuth: { type: Object, default: () => ({}) },
|
||||||
|
mobile3Verify: { type: Object, default: () => ({}) },
|
||||||
|
mobile4Verify: { type: Object, default: () => ({}) },
|
||||||
|
mobileDuration: { type: Object, default: () => ({}) },
|
||||||
|
courtRisk: { type: Object, default: () => ({}) },
|
||||||
|
personalLawsuit: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const maskedName = computed(() => maskName(props.params.name));
|
||||||
|
const maskedIdCard = computed(() => maskIdCard(props.params.id_card));
|
||||||
|
const maskedMobile = computed(() => maskMobile(props.params.mobile));
|
||||||
|
|
||||||
|
const durationText = computed(() => {
|
||||||
|
const range = props.mobileDuration.range || '';
|
||||||
|
if (range.includes('[24')) return '24~36月';
|
||||||
|
return range || '—';
|
||||||
|
});
|
||||||
|
|
||||||
|
const courtItems = computed(() =>
|
||||||
|
buildCourtRiskItems(props.courtRisk, props.personalLawsuit),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.info-card { margin-bottom: 0; }
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label { color: #666; flex-shrink: 0; }
|
||||||
|
</style>
|
||||||
87
src/ui/DWBG9FB3/components/ComplaintRiskSection.vue
Normal file
87
src/ui/DWBG9FB3/components/ComplaintRiskSection.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card">
|
||||||
|
<div class="gamma-title"><span>📞</span> 投诉风险筛查</div>
|
||||||
|
<p class="gamma-small" style="margin-bottom: 16px;">风险分: 0-100分,分数越高,投诉风险越高</p>
|
||||||
|
<div class="complaint-content">
|
||||||
|
<div class="complaint-score-card">
|
||||||
|
<div class="gamma-subtitle"><span>⧉</span> 筛查结果</div>
|
||||||
|
<div class="complaint-risk-circle">
|
||||||
|
<div class="circle" />
|
||||||
|
<div class="complaint-risk-text">
|
||||||
|
<div class="score">{{ data.score ?? '—' }}</div>
|
||||||
|
<div class="gamma-small">风险分</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<table class="gamma-table">
|
||||||
|
<thead>
|
||||||
|
<tr><th>规则名称</th><th>权重</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>是否建议拨打电话</td><td>{{ data.is_call ? '是' : '否' }}</td></tr>
|
||||||
|
<tr><td>是否高频</td><td>{{ data.is_gp ? '是' : '否' }}</td></tr>
|
||||||
|
<tr><td>是否靓号</td><td>{{ data.is_lh ? '是' : '否' }}</td></tr>
|
||||||
|
<tr><td>近14天其他类来电次数</td><td>{{ data.other_times?.day_14 ?? '—' }}</td></tr>
|
||||||
|
<tr><td>近14天金融类来电次数</td><td>{{ data.finance_times?.day_14 ?? '—' }}</td></tr>
|
||||||
|
<tr><td>用户接受意愿登记</td><td>{{ dncText }}</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { dncRegisterText } from '../reportHelper';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const dncText = computed(() => dncRegisterText(props.data.dnc));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.complaint-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-score-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-risk-circle {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-risk-circle .circle {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border: 6px solid #eee;
|
||||||
|
border-top-color: #66cc99;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.complaint-risk-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score { font-size: 28px; font-weight: 600; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.complaint-content { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
462
src/ui/DWBG9FB3/components/CreditPanoramaSection.vue
Normal file
462
src/ui/DWBG9FB3/components/CreditPanoramaSection.vue
Normal file
@@ -0,0 +1,462 @@
|
|||||||
|
<template>
|
||||||
|
<div class="panorama-report">
|
||||||
|
<div class="panorama-main-title"><span>🛡️</span> 信用全景扫描</div>
|
||||||
|
|
||||||
|
<!-- 第一行:指数 + 机构借贷情况 -->
|
||||||
|
<div class="panorama-row">
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="score-grid">
|
||||||
|
<div v-for="item in CREDIT_PANORAMA_SCORES" :key="item.key" class="score-item">
|
||||||
|
<div class="score-label">{{ item.label }}</div>
|
||||||
|
<div class="circle-index">
|
||||||
|
<span :class="['score-value', { muted: formatModelScore(data[item.key]) === '未命中' }]">
|
||||||
|
{{ formatModelScore(data[item.key]) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="score-hint">350-950,指数越大逾期率越低</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="block-title"><span>🏦</span> 机构借贷情况</div>
|
||||||
|
<div class="mini-grid">
|
||||||
|
<div v-for="item in CREDIT_PANORAMA_INSTITUTIONS" :key="item.key" class="mini-box">
|
||||||
|
<div class="mini-box-head">
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
<span
|
||||||
|
class="gamma-tag"
|
||||||
|
:class="{
|
||||||
|
'gamma-tag--danger': getPanoramaRiskTag(data[item.key]).danger,
|
||||||
|
'gamma-tag--warn': getPanoramaRiskTag(data[item.key]).warn,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ getPanoramaRiskTag(data[item.key]).label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mini-box-value">{{ formatPanoramaCount(data[item.key], item.unit) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二行:近期贷款申请 + 还款历史 -->
|
||||||
|
<div class="panorama-row">
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="block-title"><span>⏱️</span> 近期贷款申请</div>
|
||||||
|
<div class="list-rows">
|
||||||
|
<div v-for="item in CREDIT_PANORAMA_RECENT_LOAN" :key="item.key" class="list-row">
|
||||||
|
<div class="list-row-left">
|
||||||
|
<span
|
||||||
|
class="gamma-tag tag-inline"
|
||||||
|
:class="{
|
||||||
|
'gamma-tag--danger': getPanoramaRiskTag(data[item.key]).danger,
|
||||||
|
'gamma-tag--warn': getPanoramaRiskTag(data[item.key]).warn,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ getPanoramaRiskTag(data[item.key]).label }}
|
||||||
|
</span>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="list-row-value">{{ formatPanoramaCount(data[item.key], item.unit) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="block-title"><span>📋</span> 还款历史</div>
|
||||||
|
<div class="stat-grid-2">
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">历史贷款机构成功还款笔数</div>
|
||||||
|
<div class="stat-value">{{ formatPanoramaCount(data.xyp_cpl0014, ' 笔') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">历史贷款机构交易失败笔数</div>
|
||||||
|
<div class="stat-value">{{ formatPanoramaCount(data.xyp_cpl0015, ' 笔') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-rows">
|
||||||
|
<div class="list-row">
|
||||||
|
<span>90天还款成功率</span>
|
||||||
|
<span class="list-row-value">{{ formatPanoramaRatio(data.xyp_cpl0080) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-row">
|
||||||
|
<span>近90天内还款中成功还款总金额比例</span>
|
||||||
|
<span class="list-row-value">{{ formatPanoramaRatio(data.xyp_cpl0079) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-row">
|
||||||
|
<span>近5次还款中成功还款总金额比例</span>
|
||||||
|
<span class="list-row-value">{{ formatPanoramaRatio(data.xyp_cpl0073) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="list-row">
|
||||||
|
<span>近5次还款中还款成功笔数比例</span>
|
||||||
|
<span class="list-row-value">{{ formatPanoramaRatio(data.xyp_cpl0074) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第三行:交易失败 + 交易失败后还款 -->
|
||||||
|
<div class="panorama-row">
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="block-title"><span>⚠️</span> 交易失败情况</div>
|
||||||
|
<div class="list-rows">
|
||||||
|
<div v-for="item in CREDIT_PANORAMA_FAIL_COUNTS" :key="item.key" class="list-row">
|
||||||
|
<div class="list-row-left">
|
||||||
|
<span
|
||||||
|
class="gamma-tag tag-inline"
|
||||||
|
:class="{
|
||||||
|
'gamma-tag--danger': getPanoramaRiskTag(data[item.key]).danger,
|
||||||
|
'gamma-tag--warn': getPanoramaRiskTag(data[item.key]).warn,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ getPanoramaRiskTag(data[item.key]).label }}
|
||||||
|
</span>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="list-row-value">{{ formatPanoramaCount(data[item.key], item.unit) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="block-title"><span>✅</span> 交易失败后还款情况</div>
|
||||||
|
<div class="list-rows">
|
||||||
|
<div v-for="item in CREDIT_PANORAMA_FAIL_RECOVERY" :key="item.key" class="list-row">
|
||||||
|
<div class="list-row-left">
|
||||||
|
<span
|
||||||
|
class="gamma-tag tag-inline"
|
||||||
|
:class="{
|
||||||
|
'gamma-tag--danger': getPanoramaRiskTag(data[item.key]).danger,
|
||||||
|
'gamma-tag--warn': getPanoramaRiskTag(data[item.key]).warn,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ getPanoramaRiskTag(data[item.key]).label }}
|
||||||
|
</span>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="list-row-value">{{ formatPanoramaCount(data[item.key], item.unit) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第四行:逾期情况 + 贷款整体情况 -->
|
||||||
|
<div class="panorama-row">
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="block-title"><span>🚨</span> 逾期情况</div>
|
||||||
|
<div class="stat-grid-2">
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">当前逾期机构数</div>
|
||||||
|
<div class="stat-value">{{ formatPanoramaCount(data.xyp_cpl0071, '家') }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-box">
|
||||||
|
<div class="stat-label">当前逾期金额</div>
|
||||||
|
<div class="stat-value">{{ formatPanoramaAmount(data.xyp_cpl0072) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-rows">
|
||||||
|
<div v-for="item in CREDIT_PANORAMA_OVERDUE_FLAGS" :key="item.key" class="list-row flag-row">
|
||||||
|
<div class="list-row-left">
|
||||||
|
<span class="success-check">✓</span>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="list-row-value"
|
||||||
|
:class="{ 'gamma-text-danger': data[item.key] === '1' }"
|
||||||
|
>
|
||||||
|
{{ formatPanoramaYesNo(data[item.key]) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panorama-card">
|
||||||
|
<div class="block-title"><span>📈</span> 贷款整体情况</div>
|
||||||
|
<div class="list-rows">
|
||||||
|
<div v-for="item in CREDIT_PANORAMA_LOAN_OVERVIEW" :key="item.key" class="list-row flag-row">
|
||||||
|
<div class="list-row-left">
|
||||||
|
<span class="success-check">✓</span>
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="list-row-value">
|
||||||
|
{{
|
||||||
|
item.type === 'amount'
|
||||||
|
? formatPanoramaAmount(data[item.key])
|
||||||
|
: formatPanoramaCount(data[item.key], item.unit)
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第五行:贷款分周期汇总 -->
|
||||||
|
<div class="panorama-card panorama-card--full">
|
||||||
|
<div class="block-title"><span>📊</span> 贷款分周期汇总</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table class="gamma-table panorama-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>周期</th>
|
||||||
|
<th>贷款机构数</th>
|
||||||
|
<th>还款成功笔数</th>
|
||||||
|
<th>还款成功总金额(元)</th>
|
||||||
|
<th>交易失败笔数</th>
|
||||||
|
<th>交易失败总金额(元)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in CREDIT_PANORAMA_PERIOD_ROWS" :key="row.label">
|
||||||
|
<td>{{ row.label }}</td>
|
||||||
|
<td>{{ panoramaTableCell(data, row.instKey) }}</td>
|
||||||
|
<td>{{ panoramaTableCell(data, row.successKey) }}</td>
|
||||||
|
<td>{{ panoramaTableCell(data, row.successAmtKey, 'amount') }}</td>
|
||||||
|
<td>{{ panoramaTableCell(data, row.failKey) }}</td>
|
||||||
|
<td>{{ panoramaTableCell(data, row.failAmtKey, 'amount') }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
CREDIT_PANORAMA_SCORES,
|
||||||
|
CREDIT_PANORAMA_INSTITUTIONS,
|
||||||
|
CREDIT_PANORAMA_RECENT_LOAN,
|
||||||
|
CREDIT_PANORAMA_FAIL_COUNTS,
|
||||||
|
CREDIT_PANORAMA_FAIL_RECOVERY,
|
||||||
|
CREDIT_PANORAMA_OVERDUE_FLAGS,
|
||||||
|
CREDIT_PANORAMA_LOAN_OVERVIEW,
|
||||||
|
CREDIT_PANORAMA_PERIOD_ROWS,
|
||||||
|
formatModelScore,
|
||||||
|
formatPanoramaCount,
|
||||||
|
formatPanoramaAmount,
|
||||||
|
formatPanoramaRatio,
|
||||||
|
formatPanoramaYesNo,
|
||||||
|
getPanoramaRiskTag,
|
||||||
|
panoramaTableCell,
|
||||||
|
} from '../reportHelper';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.panorama-report {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-main-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #333;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 4px solid #d4af37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
&--full {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-item {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #444;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-index {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border: 4px solid #f0f0f0;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #2da44e;
|
||||||
|
|
||||||
|
&.muted {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-box {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-box-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-box-value {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-row-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-inline {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-row-value {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-grid-2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-box {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-check {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: #2da44e;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-row .list-row-left span:last-child {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panorama-table {
|
||||||
|
min-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.panorama-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
219
src/ui/DWBG9FB3/components/FraudBlacklistSection.vue
Normal file
219
src/ui/DWBG9FB3/components/FraudBlacklistSection.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card">
|
||||||
|
<div class="gamma-title"><span>👤</span> 欺诈黑名单</div>
|
||||||
|
<div class="gamma-subtitle"><span>⧉</span> 风险等级</div>
|
||||||
|
<div class="fraud-risk-circle">
|
||||||
|
<div class="circle" />
|
||||||
|
<div class="fraud-risk-text">
|
||||||
|
<div class="risk-level">{{ gradeText }}</div>
|
||||||
|
<div class="gamma-small">风险等级</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gamma-subtitle"><span>⧉</span> 命中统计</div>
|
||||||
|
<div class="fraud-stats-grid">
|
||||||
|
<div class="chart-card">
|
||||||
|
<div class="chart-title">命中总次数</div>
|
||||||
|
<div class="chart-bars">
|
||||||
|
<div v-for="bar in hitBars" :key="bar.label" class="bar-group">
|
||||||
|
<div class="bar-value">{{ bar.value }}</div>
|
||||||
|
<div class="bar" :style="{ height: bar.height + 'px' }" />
|
||||||
|
<div class="bar-label">{{ bar.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-card">
|
||||||
|
<div class="chart-title">命中机构数</div>
|
||||||
|
<div class="chart-bars">
|
||||||
|
<div v-for="bar in orgBars" :key="bar.label" class="bar-group">
|
||||||
|
<div class="bar-value">{{ bar.value }}</div>
|
||||||
|
<div class="bar" :style="{ height: bar.height + 'px' }" />
|
||||||
|
<div class="bar-label">{{ bar.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="gamma-subtitle"><span>⧉</span> 命中分布</div>
|
||||||
|
<div class="chart-card">
|
||||||
|
<div class="distribution-bars">
|
||||||
|
<div v-for="(dist, i) in distributions" :key="i" class="distribution-bar-group">
|
||||||
|
<div class="distribution-bar blue" :style="{ height: dist.d30 + 'px' }" />
|
||||||
|
<div class="distribution-bar orange" :style="{ height: dist.d90 + 'px' }" />
|
||||||
|
<div class="distribution-bar yellow" :style="{ height: dist.d180 + 'px' }" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="distribution-labels">
|
||||||
|
<div v-for="label in FRAUD_DIST_LABELS" :key="label">{{ label }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-legend">
|
||||||
|
<div class="legend-item"><div class="legend-color blue" /><span>近30天</span></div>
|
||||||
|
<div class="legend-item"><div class="legend-color orange" /><span>近90天</span></div>
|
||||||
|
<div class="legend-item"><div class="legend-color yellow" /><span>近180天</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { FRAUD_DIST_LABELS, FRAUD_DIST_KEYS, fraudGradeText } from '../reportHelper';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const gradeText = computed(() => fraudGradeText(props.data.grade));
|
||||||
|
|
||||||
|
function barHeight(val, max) {
|
||||||
|
if (!max) return 0;
|
||||||
|
return Math.max(4, Math.round((Number(val) || 0) / max * 120));
|
||||||
|
}
|
||||||
|
|
||||||
|
const hitBars = computed(() => {
|
||||||
|
const d = props.data;
|
||||||
|
const values = [
|
||||||
|
{ label: '近30天', value: d.ha_30d_C ?? 0 },
|
||||||
|
{ label: '近90天', value: d.ha_90d_C ?? 0 },
|
||||||
|
{ label: '近180天', value: d.ha_180d_C ?? 0 },
|
||||||
|
];
|
||||||
|
const max = Math.max(...values.map((v) => Number(v.value)), 1);
|
||||||
|
return values.map((v) => ({ ...v, height: barHeight(v.value, max) }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgBars = computed(() => {
|
||||||
|
const d = props.data;
|
||||||
|
const values = [
|
||||||
|
{ label: '近30天', value: d.ha_30d_J ?? 0 },
|
||||||
|
{ label: '近90天', value: d.ha_90d_J ?? 0 },
|
||||||
|
{ label: '近180天', value: d.ha_180d_J ?? 0 },
|
||||||
|
];
|
||||||
|
const max = Math.max(...values.map((v) => Number(v.value)), 1);
|
||||||
|
return values.map((v) => ({ ...v, height: barHeight(v.value, max) }));
|
||||||
|
});
|
||||||
|
|
||||||
|
const distributions = computed(() => {
|
||||||
|
const d = props.data;
|
||||||
|
const max = Math.max(
|
||||||
|
...FRAUD_DIST_KEYS.flatMap((k) => [d[k.d30], d[k.d90], d[k.d180]].map(Number)),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
return FRAUD_DIST_KEYS.map((k) => ({
|
||||||
|
d30: barHeight(d[k.d30], max),
|
||||||
|
d90: barHeight(d[k.d90], max),
|
||||||
|
d180: barHeight(d[k.d180], max),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.fraud-risk-circle {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
margin: 0 auto 24px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fraud-risk-circle .circle {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
border: 8px solid #eee;
|
||||||
|
border-top-color: #fdd860;
|
||||||
|
border-right-color: #fdd860;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fraud-risk-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-level { font-size: 20px; font-weight: 600; color: #fdd860; }
|
||||||
|
|
||||||
|
.fraud-stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-title { text-align: center; margin-bottom: 12px; font-size: 14px; }
|
||||||
|
|
||||||
|
.chart-bars {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-group { display: flex; flex-direction: column; align-items: center; gap: 4px; }
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
width: 30px;
|
||||||
|
background: #4a6fd4;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-label, .bar-value { font-size: 12px; color: #666; }
|
||||||
|
|
||||||
|
.distribution-bars {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 160px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distribution-bar-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distribution-bar {
|
||||||
|
width: 20px;
|
||||||
|
border-radius: 2px 2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distribution-bar.blue { background: #4a6fd4; }
|
||||||
|
.distribution-bar.orange { background: #f28534; }
|
||||||
|
.distribution-bar.yellow { background: #f7bc0c; }
|
||||||
|
|
||||||
|
.distribution-labels {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item { display: flex; align-items: center; gap: 4px; }
|
||||||
|
|
||||||
|
.legend-color {
|
||||||
|
width: 12px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-color.blue { background: #4a6fd4; }
|
||||||
|
.legend-color.orange { background: #f28534; }
|
||||||
|
.legend-color.yellow { background: #f7bc0c; }
|
||||||
|
</style>
|
||||||
984
src/ui/DWBG9FB3/components/JudicialCaseSection.vue
Normal file
984
src/ui/DWBG9FB3/components/JudicialCaseSection.vue
Normal file
@@ -0,0 +1,984 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card judicial-section">
|
||||||
|
<div class="gamma-title"><span>🗺️</span> 司法案件</div>
|
||||||
|
|
||||||
|
<!-- 案件概览 -->
|
||||||
|
<div class="block-title">案件概览</div>
|
||||||
|
<div class="case-overview">
|
||||||
|
<div
|
||||||
|
v-for="item in caseTypeCards"
|
||||||
|
:key="item.key"
|
||||||
|
class="overview-card"
|
||||||
|
:class="{ criminal: item.count > 0 && item.key === 'criminal' }"
|
||||||
|
>
|
||||||
|
<div v-if="item.count > 0 && item.key === 'criminal'" class="badge">!</div>
|
||||||
|
<div class="card-title">{{ item.label }}</div>
|
||||||
|
<div class="card-num">{{ item.count }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 司法风险 -->
|
||||||
|
<div class="risk-section">
|
||||||
|
<div class="risk-item">
|
||||||
|
<div class="label">司法风险<br>被告案件数</div>
|
||||||
|
<div class="bar-wrap">
|
||||||
|
<div class="bar-fill" :style="{ width: defendantBarWidth }" />
|
||||||
|
</div>
|
||||||
|
<div class="legend">
|
||||||
|
<span class="unresolved">未结案{{ count.count_wei_beigao ?? 0 }}件</span>
|
||||||
|
<span class="resolved">已经结案{{ count.count_jie_beigao ?? 0 }}件</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-hint">共{{ count.count_beigao ?? 0 }}件</div>
|
||||||
|
</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<div class="label">被告案件金额</div>
|
||||||
|
<div class="num-row">
|
||||||
|
<span class="icon-yen">¥</span>
|
||||||
|
<span>{{ count.money_beigao ?? 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<div class="label">被执行案件数</div>
|
||||||
|
<div class="num-row red">
|
||||||
|
<span>📋</span>
|
||||||
|
<span>{{ implementCount }}件</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="risk-item">
|
||||||
|
<div class="label">最近案件年份</div>
|
||||||
|
<div class="num-row red">
|
||||||
|
<span>🗓️</span>
|
||||||
|
<span>{{ recentYearText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 案件统计 -->
|
||||||
|
<div class="block-title">案件统计</div>
|
||||||
|
<div class="case-stats">
|
||||||
|
<div class="chart-title">案件数量</div>
|
||||||
|
<div class="bar-chart">
|
||||||
|
<div class="chart-legend">
|
||||||
|
<span class="unresolved">
|
||||||
|
未结案统计
|
||||||
|
<em>{{ count.count_wei_total ?? 0 }}件</em>
|
||||||
|
</span>
|
||||||
|
<span class="resolved">
|
||||||
|
已结案统计
|
||||||
|
<em>{{ count.count_jie_total ?? 0 }}件</em>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="bar-rows">
|
||||||
|
<div v-for="row in roleBarRows" :key="row.label" class="bar-row">
|
||||||
|
<div class="row-label">{{ row.label }}</div>
|
||||||
|
<div class="row-bar-track">
|
||||||
|
<div class="row-bar" :style="{ width: row.width }" />
|
||||||
|
</div>
|
||||||
|
<div class="row-value">{{ row.count }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="axis">
|
||||||
|
<span v-for="tick in axisTicks" :key="tick">{{ tick }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chart-title">涉案金额</div>
|
||||||
|
<div class="amount-section">
|
||||||
|
<div v-for="card in amountCards" :key="card.title" class="amount-card">
|
||||||
|
<div class="card-header">
|
||||||
|
<span>{{ card.icon }}</span>
|
||||||
|
<span>{{ card.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="row"><span>总案件</span><span class="value">{{ card.total }}</span></div>
|
||||||
|
<div class="row"><span>已结案</span><span class="value">{{ card.jie }}</span></div>
|
||||||
|
<div class="row"><span>未结案</span><span class="value">{{ card.wei }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pie-section">
|
||||||
|
<div v-for="pie in pieCharts" :key="pie.title" class="pie-card">
|
||||||
|
<div class="pie-container">
|
||||||
|
<div class="pie-circle" :class="{ simple: pie.items.length <= 1 }" :style="{ background: pie.gradient }">
|
||||||
|
<div class="inner">
|
||||||
|
{{ pie.title }}<br>占比图
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="pie.items.length" class="pie-legend">
|
||||||
|
<div v-for="(item, i) in pie.items" :key="i" class="pie-legend-item">
|
||||||
|
<span class="dot" :style="{ background: pieColors[i % pieColors.length] }" />
|
||||||
|
{{ item.label }}({{ item.count }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 案件列表(可展开详情) -->
|
||||||
|
<div v-if="allCases.length" class="case-list-card">
|
||||||
|
<div
|
||||||
|
v-for="(c, i) in allCases"
|
||||||
|
:key="`${c.c_ah}-${i}`"
|
||||||
|
class="case-list-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="case-item"
|
||||||
|
:class="{ active: expandedIndex === i }"
|
||||||
|
@click="toggleCase(i)"
|
||||||
|
>
|
||||||
|
<span class="case-text">{{ caseListText(c) }}</span>
|
||||||
|
<span class="case-arrow">{{ expandedIndex === i ? '∨' : '>' }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="expandedIndex === i" class="case-block">
|
||||||
|
<div class="case-info">
|
||||||
|
<div><span>案件类型:</span><span>{{ c.sectionLabel }}</span></div>
|
||||||
|
<div><span>诉讼地位:</span><span>{{ c.n_ssdw }}</span></div>
|
||||||
|
<div><span>所属地域:</span><span>{{ c.c_ssdy }}</span></div>
|
||||||
|
<div><span>当事人:</span><span>{{ partiesText(c.c_dsrxx) }}</span></div>
|
||||||
|
<div><span>审理程序:</span><span>{{ c.n_slcx }}</span></div>
|
||||||
|
<div><span>立案时间:</span><span>{{ c.d_larq }}</span></div>
|
||||||
|
<div><span>结束时间:</span><span>{{ c.d_jarq }}</span></div>
|
||||||
|
<div><span>立案案由详情:</span><span>{{ c.n_laay_tree }}</span></div>
|
||||||
|
<div><span>结案方式:</span><span>{{ c.n_jafs }}</span></div>
|
||||||
|
<div><span>经办法院:</span><span>{{ c.n_jbfy }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div v-if="c.c_gkws_pjjg" class="case-judgment gamma-small">
|
||||||
|
判决结果:<br>{{ c.c_gkws_pjjg }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 失信被执行人 -->
|
||||||
|
<div class="module-card">
|
||||||
|
<div class="module-title title-dishonest">失信被执行人</div>
|
||||||
|
<template v-if="dishonestList.length">
|
||||||
|
<div class="case-list-card inner-list">
|
||||||
|
<div v-for="(item, i) in dishonestList" :key="i" class="case-item static">
|
||||||
|
<span class="case-text">{{ dishonestText(item) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="empty-data">
|
||||||
|
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 7C3 5.89543 3.89543 5 5 5H19C20.1046 5 21 5.89543 21 7V17C21 18.1046 20.1046 19 19 19H5C3.89543 19 3 18.1046 3 17V7Z" stroke="#999" stroke-width="1.5" />
|
||||||
|
<path d="M3 7L12 12L21 7" stroke="#999" stroke-width="1.5" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
<span>暂无数据</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 限高被执行人 -->
|
||||||
|
<div class="module-card">
|
||||||
|
<div class="module-title title-high-limit">限高被执行人</div>
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>案号</th>
|
||||||
|
<th>企业法人</th>
|
||||||
|
<th>企业名称</th>
|
||||||
|
<th>执行法院</th>
|
||||||
|
<th>发布时间(日期)</th>
|
||||||
|
<th>立案时间(日期)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<template v-if="limitHighList.length">
|
||||||
|
<tr v-for="(item, i) in limitHighRows" :key="i">
|
||||||
|
<td>{{ item.ah }}</td>
|
||||||
|
<td>{{ item.legalPerson }}</td>
|
||||||
|
<td>{{ item.entName }}</td>
|
||||||
|
<td>{{ item.court }}</td>
|
||||||
|
<td>{{ item.publishDate }}</td>
|
||||||
|
<td>{{ item.filingDate }}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<tr v-else>
|
||||||
|
<td colspan="6">
|
||||||
|
<div class="empty-data">
|
||||||
|
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M3 7C3 5.89543 3.89543 5 5 5H19C20.1046 5 21 5.89543 21 7V17C21 18.1046 20.1046 19 19 19H5C3.89543 19 3 18.1046 3 17V7Z" stroke="#999" stroke-width="1.5" />
|
||||||
|
<path d="M3 7L12 12L21 7" stroke="#999" stroke-width="1.5" stroke-linecap="round" />
|
||||||
|
</svg>
|
||||||
|
<span>暂无数据</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 报告使用须知 -->
|
||||||
|
<div class="notice-card">
|
||||||
|
<div class="notice-title">报告使用须知:</div>
|
||||||
|
<ol class="notice-list">
|
||||||
|
<li v-for="(line, i) in REPORT_USAGE_NOTICE" :key="i">{{ line }}</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import {
|
||||||
|
parseStatDistribution,
|
||||||
|
buildConicGradient,
|
||||||
|
toCaseArray,
|
||||||
|
extractJudicialList,
|
||||||
|
REPORT_USAGE_NOTICE,
|
||||||
|
caseListText,
|
||||||
|
limitHighRow,
|
||||||
|
} from '../reportHelper';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const PIE_COLORS = ['#4E74F6', '#89d079', '#f6c358', '#ff7c7c', '#9b59b6', '#5dade2'];
|
||||||
|
const pieColors = PIE_COLORS;
|
||||||
|
|
||||||
|
const count = computed(() => props.data.count || {});
|
||||||
|
|
||||||
|
const caseTypeCards = computed(() => [
|
||||||
|
{ key: 'administrative', label: '行政案件', count: toCaseArray(props.data.administrative).length },
|
||||||
|
{ key: 'civil', label: '民事案件', count: toCaseArray(props.data.civil).length },
|
||||||
|
{ key: 'criminal', label: '刑事案件', count: toCaseArray(props.data.criminal).length },
|
||||||
|
{ key: 'implement', label: '执行案件', count: toCaseArray(props.data.implement).length },
|
||||||
|
{ key: 'preservation', label: '非诉保全审查', count: toCaseArray(props.data.preservation).length },
|
||||||
|
{ key: 'bankrupt', label: '强制清算与破产案件', count: toCaseArray(props.data.bankrupt).length },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const implementCount = computed(() => toCaseArray(props.data.implement).length);
|
||||||
|
|
||||||
|
const defendantBarWidth = computed(() => {
|
||||||
|
const total = Number(count.value.count_beigao) || 0;
|
||||||
|
const jie = Number(count.value.count_jie_beigao) || 0;
|
||||||
|
if (!total) return '0%';
|
||||||
|
return `${Math.round((jie / total) * 100)}%`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const recentYearText = computed(() => {
|
||||||
|
const stat = count.value.larq_stat || '';
|
||||||
|
const items = parseStatDistribution(stat);
|
||||||
|
if (!items.length) return '—';
|
||||||
|
const latest = items.reduce((a, b) => (a.label > b.label ? a : b));
|
||||||
|
return `${latest.label}(${latest.count})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const roleBarRows = computed(() => {
|
||||||
|
const c = count.value;
|
||||||
|
const rows = [
|
||||||
|
{ label: '被告', count: Number(c.count_beigao) || 0 },
|
||||||
|
{ label: '原告', count: Number(c.count_yuangao) || 0 },
|
||||||
|
{ label: '第三人', count: Number(c.count_other) || 0 },
|
||||||
|
];
|
||||||
|
const max = Math.max(...rows.map((r) => r.count), 1);
|
||||||
|
return rows.map((r) => ({
|
||||||
|
...r,
|
||||||
|
width: `${Math.round((r.count / max) * 100)}%`,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const axisTicks = computed(() => {
|
||||||
|
const max = Math.max(...roleBarRows.value.map((r) => r.count), 2);
|
||||||
|
const step = max <= 2 ? 0.5 : 1;
|
||||||
|
const ticks = [];
|
||||||
|
for (let i = 0; i <= max; i += step) {
|
||||||
|
ticks.push(Number.isInteger(i) ? i : i.toFixed(1));
|
||||||
|
}
|
||||||
|
return ticks.length > 1 ? ticks : [0, 0.5, 1, 1.5, 2, 2.5];
|
||||||
|
});
|
||||||
|
|
||||||
|
const amountCards = computed(() => {
|
||||||
|
const c = count.value;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: '汇总',
|
||||||
|
icon: '📊',
|
||||||
|
total: c.money_total ?? 0,
|
||||||
|
jie: c.money_jie_total ?? 0,
|
||||||
|
wei: c.money_wei_total ?? 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '被告',
|
||||||
|
icon: '👤',
|
||||||
|
total: c.money_beigao ?? 0,
|
||||||
|
jie: c.money_jie_beigao ?? 0,
|
||||||
|
wei: c.money_wei_beigao ?? 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '原告',
|
||||||
|
icon: '👤',
|
||||||
|
total: c.money_yuangao ?? 0,
|
||||||
|
jie: c.money_jie_yuangao ?? 0,
|
||||||
|
wei: c.money_wei_yuangao ?? 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '第三人',
|
||||||
|
icon: '👤',
|
||||||
|
total: c.money_other ?? 0,
|
||||||
|
jie: c.money_jie_other ?? 0,
|
||||||
|
wei: c.money_wei_other ?? 0,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const pieCharts = computed(() => {
|
||||||
|
const c = count.value;
|
||||||
|
return [
|
||||||
|
{ title: '地点分布', items: parseStatDistribution(c.area_stat), gradient: '' },
|
||||||
|
{ title: '案由分布', items: parseStatDistribution(c.ay_stat), gradient: '' },
|
||||||
|
{ title: '结案分布', items: parseStatDistribution(c.jafs_stat), gradient: '' },
|
||||||
|
{ title: '时间分布', items: parseStatDistribution(c.larq_stat), gradient: '' },
|
||||||
|
].map((pie) => ({
|
||||||
|
...pie,
|
||||||
|
gradient: buildConicGradient(pie.items),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
const caseSections = computed(() => {
|
||||||
|
const sections = [
|
||||||
|
{ key: 'criminal', label: '刑事案件', items: toCaseArray(props.data.criminal) },
|
||||||
|
{ key: 'civil', label: '民事案件', items: toCaseArray(props.data.civil) },
|
||||||
|
{ key: 'administrative', label: '行政案件', items: toCaseArray(props.data.administrative) },
|
||||||
|
{ key: 'implement', label: '执行案件', items: toCaseArray(props.data.implement) },
|
||||||
|
];
|
||||||
|
return sections.filter((s) => s.items.length > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const allCases = computed(() => {
|
||||||
|
const list = [];
|
||||||
|
for (const section of caseSections.value) {
|
||||||
|
for (const c of section.items) {
|
||||||
|
list.push({ ...c, sectionLabel: section.label });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
|
||||||
|
const expandedIndex = ref(null);
|
||||||
|
|
||||||
|
function toggleCase(index) {
|
||||||
|
expandedIndex.value = expandedIndex.value === index ? null : index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dishonestList = computed(() =>
|
||||||
|
extractJudicialList(props.data, [
|
||||||
|
'dishonest',
|
||||||
|
'sxbzxr',
|
||||||
|
'breachCase',
|
||||||
|
'disinCases',
|
||||||
|
'dishonestExecutor',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const limitHighList = computed(() =>
|
||||||
|
extractJudicialList(props.data, [
|
||||||
|
'limitHigh',
|
||||||
|
'xgbzxr',
|
||||||
|
'consumptionRestriction',
|
||||||
|
'limitCases',
|
||||||
|
'limitExecutor',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const limitHighRows = computed(() => limitHighList.value.map(limitHighRow));
|
||||||
|
|
||||||
|
function dishonestText(item) {
|
||||||
|
const ah = item.c_ah || item.ah || item.caseCode || '—';
|
||||||
|
const court = item.n_jbfy || item.court || item.zxfy || '';
|
||||||
|
return court ? `${ah}(${court})` : ah;
|
||||||
|
}
|
||||||
|
|
||||||
|
function partiesText(dsrxx) {
|
||||||
|
if (!Array.isArray(dsrxx)) return '—';
|
||||||
|
return dsrxx.map((d) => `${d.c_mc}【${d.n_dsrlx}】`).join('; ');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.judicial-section {
|
||||||
|
--blue: #4e74f6;
|
||||||
|
--red: #ff4d4f;
|
||||||
|
--border: #e8eef7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin: 20px 0 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%234E74F6"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/></svg>') no-repeat center;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-overview {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(6, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overview-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px 12px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
|
||||||
|
&.criminal {
|
||||||
|
background: #fff5f5;
|
||||||
|
border-color: #ffd0d0;
|
||||||
|
|
||||||
|
.card-num { color: var(--red); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: var(--red);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-num {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-item .label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--blue);
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
margin-top: 4px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend span::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend .unresolved::before { background: var(--red); }
|
||||||
|
.legend .resolved::before { background: var(--blue); }
|
||||||
|
|
||||||
|
.total-hint {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
&.red { color: var(--red); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yen { color: var(--blue); font-size: 18px; font-style: normal; }
|
||||||
|
|
||||||
|
.chart-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--blue);
|
||||||
|
border: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-chart {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend > span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend .unresolved {
|
||||||
|
background: #ff4d4f20;
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend .resolved {
|
||||||
|
background: #4e74f620;
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend em {
|
||||||
|
font-style: normal;
|
||||||
|
padding: 0 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-legend .unresolved em { background: var(--red); }
|
||||||
|
.chart-legend .resolved em { background: var(--blue); }
|
||||||
|
|
||||||
|
.bar-rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-label {
|
||||||
|
width: 60px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-bar-track {
|
||||||
|
flex: 1;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-bar {
|
||||||
|
height: 100%;
|
||||||
|
background: var(--blue);
|
||||||
|
border-radius: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--blue);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-value {
|
||||||
|
width: 24px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.axis {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 0 calc(60px + 36px) 0 calc(60px + 12px);
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-card .card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-card .row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-card .value {
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-container {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-circle {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&.simple { background: var(--blue) !important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-circle .inner {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-legend {
|
||||||
|
margin-top: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pie-legend-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-list-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 24px 0 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-list-card.inner-list {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-list-group {
|
||||||
|
border-bottom: 1px solid #e8edf5;
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: #f0f7ff;
|
||||||
|
border-bottom: 1px solid #e8edf5;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 3px;
|
||||||
|
height: 16px;
|
||||||
|
background: #4080ff;
|
||||||
|
margin-right: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.static { cursor: default; }
|
||||||
|
&.active { background: #e6f0ff; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-arrow {
|
||||||
|
color: #999;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-block {
|
||||||
|
padding: 16px 20px 16px 33px;
|
||||||
|
background: #fafbfc;
|
||||||
|
border-top: 1px dashed #e8edf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-info div {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px dashed #eee;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-judgment {
|
||||||
|
margin-top: 10px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-dishonest::before {
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23666"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-high-limit::before {
|
||||||
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23666"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>');
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 0;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table th {
|
||||||
|
background: #fff9e8;
|
||||||
|
padding: 12px 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td {
|
||||||
|
padding: 12px 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-list {
|
||||||
|
list-style-type: decimal;
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.case-overview { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
.risk-section { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.amount-section,
|
||||||
|
.pie-section { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.case-overview { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
.risk-section,
|
||||||
|
.amount-section,
|
||||||
|
.pie-section { grid-template-columns: 1fr; }
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
278
src/ui/DWBG9FB3/components/LoanIntentSection.vue
Normal file
278
src/ui/DWBG9FB3/components/LoanIntentSection.vue
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
<template>
|
||||||
|
<div class="intent-report">
|
||||||
|
<div class="section-title">借贷意向</div>
|
||||||
|
<p class="desc-text">
|
||||||
|
借贷意向数据覆盖大部分的金融机构。机构类型包括银行、改制机构、小贷、消费类分期、现金类分期、代偿类分期和非银其它。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="section-title">最终决策结果</div>
|
||||||
|
<div class="tab-box">
|
||||||
|
<div
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: activeTab === 'decision' }"
|
||||||
|
@click="activeTab = 'decision'"
|
||||||
|
>
|
||||||
|
{{ data.Rule_final_decision || '最终决策结果' }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: activeTab === 'review' }"
|
||||||
|
@click="activeTab = 'review'"
|
||||||
|
>
|
||||||
|
复议
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-if="data.Rule_name_QJF045" class="rule-hint">
|
||||||
|
命中规则:{{ data.Rule_name_QJF045 }}
|
||||||
|
<span v-if="data.Rule_final_weight">(权重 {{ data.Rule_final_weight }})</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- 本人在本机构借贷意向表现 -->
|
||||||
|
<div class="section-title">本人在本机构借贷意向表现</div>
|
||||||
|
<table class="intent-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="left-align">申请次数</th>
|
||||||
|
<th v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="p.prefix">{{ p.label }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in LOAN_INTENT_INST_ROWS" :key="row.label">
|
||||||
|
<td class="left-align">{{ row.label }}</td>
|
||||||
|
<td v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="`${row.label}-${p.prefix}`">
|
||||||
|
{{ formatAlsCount(data, p.prefix, row.suffix) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="note-text" v-html="LOAN_INTENT_NOTE" />
|
||||||
|
|
||||||
|
<!-- 本人在各个客户类型借贷意向表现 -->
|
||||||
|
<div class="section-title">本人在各个客户类型借贷意向表现</div>
|
||||||
|
<table class="intent-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="left-align" rowspan="2">客户类型</th>
|
||||||
|
<th v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="'h1-' + p.prefix" colspan="2">{{ p.label }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<template v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="'h2-' + p.prefix">
|
||||||
|
<th>机构数</th>
|
||||||
|
<th>次数</th>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="row in LOAN_INTENT_CUSTOMER_ROWS"
|
||||||
|
:key="row.label"
|
||||||
|
:class="{ 'light-bg': row.highlight }"
|
||||||
|
>
|
||||||
|
<td class="left-align">{{ row.label }}</td>
|
||||||
|
<template v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="`${row.label}-${p.prefix}`">
|
||||||
|
<td>{{ formatAlsOrg(data, p.prefix, row.suffix) }}</td>
|
||||||
|
<td>{{ formatAlsCount(data, p.prefix, row.suffix) }}</td>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="note-text" v-html="LOAN_INTENT_NOTE" />
|
||||||
|
|
||||||
|
<!-- 本人在各个业务类型借贷意向表现 -->
|
||||||
|
<div class="section-title">本人在各个业务类型借贷意向表现</div>
|
||||||
|
<table class="intent-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="left-align" rowspan="2">业务类型</th>
|
||||||
|
<th v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="'b1-' + p.prefix" colspan="2">{{ p.label }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<template v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="'b2-' + p.prefix">
|
||||||
|
<th>机构数</th>
|
||||||
|
<th>次数</th>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in LOAN_INTENT_BUSINESS_ROWS" :key="row.label">
|
||||||
|
<td class="left-align">{{ row.label }}</td>
|
||||||
|
<template v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="`${row.label}-${p.prefix}`">
|
||||||
|
<td>{{ formatAlsOrg(data, p.prefix, row.suffix) }}</td>
|
||||||
|
<td>{{ formatAlsCount(data, p.prefix, row.suffix) }}</td>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="note-text" v-html="LOAN_INTENT_NOTE" />
|
||||||
|
|
||||||
|
<!-- 本人在异常时间段借贷意向表现 -->
|
||||||
|
<div class="section-title">本人在异常时间段借贷意向表现</div>
|
||||||
|
<table class="intent-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="left-align" rowspan="2">时间-机构</th>
|
||||||
|
<th v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="'a1-' + p.prefix" colspan="2">{{ p.label }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<template v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="'a2-' + p.prefix">
|
||||||
|
<th>机构数</th>
|
||||||
|
<th>次数</th>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="row in LOAN_INTENT_ABNORMAL_ROWS" :key="row.label">
|
||||||
|
<td class="left-align">{{ row.label }}</td>
|
||||||
|
<template v-for="p in LOAN_INTENT_PERIOD_PREFIXES" :key="`${row.label}-${p.prefix}`">
|
||||||
|
<td>{{ formatAlsOrg(data, p.prefix, row.suffix) }}</td>
|
||||||
|
<td>{{ formatAlsCount(data, p.prefix, row.suffix) }}</td>
|
||||||
|
</template>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="note-text" v-html="LOAN_INTENT_NOTE" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import {
|
||||||
|
LOAN_INTENT_PERIOD_PREFIXES,
|
||||||
|
LOAN_INTENT_INST_ROWS,
|
||||||
|
LOAN_INTENT_CUSTOMER_ROWS,
|
||||||
|
LOAN_INTENT_BUSINESS_ROWS,
|
||||||
|
LOAN_INTENT_ABNORMAL_ROWS,
|
||||||
|
LOAN_INTENT_NOTE,
|
||||||
|
formatAlsOrg,
|
||||||
|
formatAlsCount,
|
||||||
|
} from '../reportHelper';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeTab = ref('review');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.intent-report {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 30px 0 16px;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 4px solid #d4af37;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 16px;
|
||||||
|
background: linear-gradient(135deg, #d4af37 30%, transparent 30%, transparent 60%, #d4af37 60%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc-text {
|
||||||
|
color: #666;
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 8px 0 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-left: 14px;
|
||||||
|
border-left: 2px solid #d4af37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-box {
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
padding: 8px 24px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-right: none;
|
||||||
|
background: #f5f7fa;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule-hint {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #d89614;
|
||||||
|
margin: 4px 0 12px;
|
||||||
|
padding-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intent-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 16px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
padding: 12px 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #fdf8e9;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-align {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-bg {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
margin: 8px 0 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.intent-report {
|
||||||
|
padding: 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intent-table {
|
||||||
|
min-width: 900px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
src/ui/DWBG9FB3/components/LoanProfileSection.vue
Normal file
80
src/ui/DWBG9FB3/components/LoanProfileSection.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card">
|
||||||
|
<div class="gamma-title"><span>🏦</span> 借贷画像</div>
|
||||||
|
<div class="loan-profile-grid">
|
||||||
|
<div v-for="item in items" :key="item.label" class="loan-item">
|
||||||
|
<div class="loan-icon" :class="item.color">{{ item.icon }}</div>
|
||||||
|
<div>
|
||||||
|
<div class="loan-text">{{ item.label }}</div>
|
||||||
|
<div class="loan-value">{{ item.value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { cellText } from '../reportHelper';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
loanTotal: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = computed(() => {
|
||||||
|
const d = props.loanTotal || {};
|
||||||
|
return [
|
||||||
|
{ label: '机构查询总次数', value: cellText(d.queryCount), icon: '📑', color: 'purple' },
|
||||||
|
{ label: '借贷机构数(2年内)', value: cellText(d.orgCount), icon: '🏢', color: 'green' },
|
||||||
|
{ label: '正常还款订单占比', value: cellText(d.repayRate), icon: '🤲', color: 'orange' },
|
||||||
|
{ label: '预估网贷授信额度', value: cellText(d.creditLimit), icon: '📄', color: 'pink' },
|
||||||
|
{ label: '网贷产品数', value: cellText(d.productCount), icon: '📦', color: 'red' },
|
||||||
|
{ label: '已结清订单数', value: cellText(d.settledCount), icon: '👤', color: 'blue' },
|
||||||
|
{ label: '借款总金额(2年内)', value: d.loanAmount != null ? `${d.loanAmount} 元` : '— 元', icon: '💴', color: 'orange' },
|
||||||
|
{ label: '逾期总金额(2年内)', value: cellText(d.overdueAmount), icon: '📄', color: 'pink' },
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.loan-profile-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loan-item {
|
||||||
|
background: #f9fafc;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loan-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loan-icon.purple { background: #6c5ce7; }
|
||||||
|
.loan-icon.green { background: #00b894; }
|
||||||
|
.loan-icon.orange { background: #fdcb6e; }
|
||||||
|
.loan-icon.pink { background: #e84393; }
|
||||||
|
.loan-icon.red { background: #e74c3c; }
|
||||||
|
.loan-icon.blue { background: #3498db; }
|
||||||
|
|
||||||
|
.loan-text { font-size: 14px; line-height: 1.4; }
|
||||||
|
.loan-value { font-size: 16px; font-weight: 600; margin-top: 4px; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.loan-profile-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
src/ui/DWBG9FB3/components/OverdueBlacklistSection.vue
Normal file
83
src/ui/DWBG9FB3/components/OverdueBlacklistSection.vue
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card">
|
||||||
|
<div class="gamma-title"><span>🚫</span> 逾期黑名单</div>
|
||||||
|
<div class="gamma-subtitle"><span>⧉</span> 命中结果</div>
|
||||||
|
<div class="blacklist-row">
|
||||||
|
<div class="blacklist-label">是否命中黑名单</div>
|
||||||
|
<div class="blacklist-value">{{ isHit(data.black_list) ? '是' : '否' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="gamma-subtitle"><span>⧉</span> 疑似标签</div>
|
||||||
|
<div class="tags-grid">
|
||||||
|
<div v-for="tag in BLACKLIST_TAGS" :key="tag.key" class="tag-item">
|
||||||
|
<div class="tag-label">{{ tag.label }}</div>
|
||||||
|
<div class="tag-value">{{ isHit(data[tag.key]) ? '命中' : '未命中' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { BLACKLIST_TAGS, isHit } from '../reportHelper';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.blacklist-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 400px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blacklist-label {
|
||||||
|
width: 160px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blacklist-value {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-left: none;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-item { display: flex; align-items: center; }
|
||||||
|
|
||||||
|
.tag-label {
|
||||||
|
width: 200px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
background: #f9fafc;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-value {
|
||||||
|
flex: 1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-left: none;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tags-grid { grid-template-columns: 1fr; }
|
||||||
|
.tag-label { width: 140px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
28
src/ui/DWBG9FB3/components/OverdueSurveySection.vue
Normal file
28
src/ui/DWBG9FB3/components/OverdueSurveySection.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card">
|
||||||
|
<div class="gamma-title"><span>🏠</span> 逾期勘测V3</div>
|
||||||
|
<div class="gamma-grid">
|
||||||
|
<div v-for="field in fields" :key="field.key" class="gamma-item">
|
||||||
|
<label>{{ field.label }}</label>
|
||||||
|
<span>{{ field.display }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { OVERDUE_SURVEY_FIELDS, cellText, overdueResultText } from '../reportHelper';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
|
||||||
|
const fields = computed(() =>
|
||||||
|
OVERDUE_SURVEY_FIELDS.map((f) => {
|
||||||
|
let display = cellText(props.data[f.key]);
|
||||||
|
if (f.key === 'result_code') display = overdueResultText(props.data[f.key]);
|
||||||
|
return { ...f, display };
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
148
src/ui/DWBG9FB3/components/RiskAssessmentSection.vue
Normal file
148
src/ui/DWBG9FB3/components/RiskAssessmentSection.vue
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card risk-assessment">
|
||||||
|
<div class="gamma-grid-2" style="grid-template-columns: 1fr 2fr; gap: 24px;">
|
||||||
|
<div class="risk-card">
|
||||||
|
<div class="gamma-title"><span>🛡️</span> 风险评估</div>
|
||||||
|
<div class="risk-desc">
|
||||||
|
说明:<br>
|
||||||
|
<span v-for="item in RISK_LEVEL_DESC" :key="item.level">
|
||||||
|
{{ item.level }}({{ item.label }}): {{ item.desc }}<br>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="risk-level-circle">
|
||||||
|
<div class="circle" :style="{ borderTopColor: levelColor }">
|
||||||
|
<div class="circle-text">{{ riskLevel }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="risk-level-text">风险等级</div>
|
||||||
|
<div class="risk-stars">{{ riskStars }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="risk-card index-group">
|
||||||
|
<div class="index-item">
|
||||||
|
<div class="index-title">信用风险指数</div>
|
||||||
|
<div class="index-circle">
|
||||||
|
<div class="index-status">{{ riskScoreDisplay }}</div>
|
||||||
|
<div class="gamma-small">{{ riskScoreUnit }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="index-desc">信用风险评分,0-1000分,分数越高用户信用越高</div>
|
||||||
|
</div>
|
||||||
|
<div class="index-item">
|
||||||
|
<div class="index-title">履约金额综合指数</div>
|
||||||
|
<div class="index-circle">
|
||||||
|
<div class="index-status">{{ amountIndexDisplay.value }}</div>
|
||||||
|
<div class="gamma-small">{{ amountIndexDisplay.unit }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="index-desc">履约金额综合指数,0-1000分,指数越大用户逾期可能性越低</div>
|
||||||
|
</div>
|
||||||
|
<div class="index-item">
|
||||||
|
<div class="index-title">履约笔数综合指数</div>
|
||||||
|
<div class="index-circle">
|
||||||
|
<div class="index-status">{{ countIndexDisplay.value }}</div>
|
||||||
|
<div class="gamma-small">{{ countIndexDisplay.unit }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="index-desc">履约笔数综合指数,0-1000分,指数越大用户逾期可能性越低</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { RISK_LEVEL_DESC, riskLevelColor, formatComplianceIndex, riskLevelStars } from '../reportHelper';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
riskLevel: { type: String, default: '—' },
|
||||||
|
riskScore: { type: [String, Number], default: '—' },
|
||||||
|
amountIndex: { type: [String, Number], default: '' },
|
||||||
|
countIndex: { type: [String, Number], default: '' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const levelColor = computed(() => riskLevelColor(props.riskLevel));
|
||||||
|
const riskStars = computed(() => riskLevelStars(props.riskLevel));
|
||||||
|
|
||||||
|
const riskScoreDisplay = computed(() => {
|
||||||
|
if (props.riskScore === null || props.riskScore === undefined || props.riskScore === '') {
|
||||||
|
return '未命中';
|
||||||
|
}
|
||||||
|
return String(props.riskScore);
|
||||||
|
});
|
||||||
|
|
||||||
|
const riskScoreUnit = computed(() => (riskScoreDisplay.value === '未命中' ? '未命中' : '分'));
|
||||||
|
|
||||||
|
const amountIndexDisplay = computed(() => formatComplianceIndex(props.amountIndex));
|
||||||
|
const countIndexDisplay = computed(() => formatComplianceIndex(props.countIndex));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.risk-assessment .risk-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-level-circle {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid #eee;
|
||||||
|
border-top-color: #ff3333;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-text {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-level-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.risk-stars {
|
||||||
|
margin-top: 4px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: #d4af37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-item { text-align: center; }
|
||||||
|
|
||||||
|
.index-circle {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-title { font-size: 14px; margin-bottom: 4px; }
|
||||||
|
.index-status { font-size: 18px; font-weight: 600; }
|
||||||
|
.index-desc { font-size: 12px; color: #666; margin-top: 4px; line-height: 1.4; max-width: 180px; }
|
||||||
|
</style>
|
||||||
70
src/ui/DWBG9FB3/components/RiskSummarySection.vue
Normal file
70
src/ui/DWBG9FB3/components/RiskSummarySection.vue
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-card">
|
||||||
|
<div class="gamma-title"><span>🏮</span> 风险汇总</div>
|
||||||
|
<div class="summary-grid">
|
||||||
|
<div v-for="cat in RISK_SUMMARY_CATEGORIES" :key="cat.key" class="summary-card">
|
||||||
|
<div class="summary-card-title">
|
||||||
|
<span>{{ cat.icon }}</span>
|
||||||
|
<span>{{ cat.title }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(item, i) in (risks[cat.key] || [])"
|
||||||
|
:key="i"
|
||||||
|
class="summary-item"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
<div v-if="!(risks[cat.key] || []).length" class="summary-empty">无风险项</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { RISK_SUMMARY_CATEGORIES } from '../reportHelper';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
risks: { type: Object, default: () => ({}) },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.summary-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-item {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #d93025;
|
||||||
|
background: #fef0f0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-empty {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.summary-grid { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
172
src/ui/DWBG9FB3/index.vue
Normal file
172
src/ui/DWBG9FB3/index.vue
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
<template>
|
||||||
|
<div class="gamma-report">
|
||||||
|
<div ref="reportRef" class="gamma-container">
|
||||||
|
<div class="report-header">
|
||||||
|
<div class="report-title">个人大数据风险档案</div>
|
||||||
|
<div class="report-time">报告输出时间: {{ reportTime }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RiskAssessmentSection
|
||||||
|
:risk-level="root.riskLevel"
|
||||||
|
:risk-score="root.riskScore"
|
||||||
|
:amount-index="root.loanRiskTagV21?.xyp_cpl0082"
|
||||||
|
:count-index="root.loanRiskTagV21?.xyp_cpl0083"
|
||||||
|
/>
|
||||||
|
<RiskSummarySection :risks="root.risks" />
|
||||||
|
<BasicInfoSection
|
||||||
|
:params="params"
|
||||||
|
:real-name-auth="root.realNameAuth"
|
||||||
|
:mobile3-verify="root.mobile3Verify"
|
||||||
|
:mobile4-verify="root.mobile4Verify"
|
||||||
|
:mobile-duration="root.mobileDuration"
|
||||||
|
:court-risk="root.courtRisk"
|
||||||
|
:personal-lawsuit="root.personalLawsuit"
|
||||||
|
/>
|
||||||
|
<LoanProfileSection :loan-total="root.loanTotal" />
|
||||||
|
<OverdueBlacklistSection :data="root.blackListV110" />
|
||||||
|
<FraudBlacklistSection :data="root.blackListV121_3" />
|
||||||
|
<ComplaintRiskSection :data="root.mobileRiskV709" />
|
||||||
|
<OverdueSurveySection :data="root.loanRiskTagV10" />
|
||||||
|
<CreditPanoramaSection :data="root.loanRiskTagV21" />
|
||||||
|
<LoanIntentSection :data="root.loanRiskTagV11" />
|
||||||
|
<JudicialCaseSection :data="root.personalLawsuit" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { parseRoot, extractReportUrl } from './reportHelper';
|
||||||
|
import { printReportAsPdf } from './reportExport';
|
||||||
|
import RiskAssessmentSection from './components/RiskAssessmentSection.vue';
|
||||||
|
import RiskSummarySection from './components/RiskSummarySection.vue';
|
||||||
|
import BasicInfoSection from './components/BasicInfoSection.vue';
|
||||||
|
import LoanProfileSection from './components/LoanProfileSection.vue';
|
||||||
|
import OverdueBlacklistSection from './components/OverdueBlacklistSection.vue';
|
||||||
|
import FraudBlacklistSection from './components/FraudBlacklistSection.vue';
|
||||||
|
import ComplaintRiskSection from './components/ComplaintRiskSection.vue';
|
||||||
|
import OverdueSurveySection from './components/OverdueSurveySection.vue';
|
||||||
|
import CreditPanoramaSection from './components/CreditPanoramaSection.vue';
|
||||||
|
import LoanIntentSection from './components/LoanIntentSection.vue';
|
||||||
|
import JudicialCaseSection from './components/JudicialCaseSection.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: { type: Object, default: () => ({}) },
|
||||||
|
params: { type: Object, default: () => ({}) },
|
||||||
|
apiId: { type: String, default: 'DWBG9FB3' },
|
||||||
|
index: { type: Number, default: 0 },
|
||||||
|
notifyRiskStatus: { type: Function, default: () => {} },
|
||||||
|
reportDateTime: { type: String, default: '' },
|
||||||
|
showToolbar: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
const root = computed(() => parseRoot(props.data));
|
||||||
|
const reportUrl = computed(() => extractReportUrl(props.data));
|
||||||
|
const reportRef = ref(null);
|
||||||
|
const exporting = ref(false);
|
||||||
|
|
||||||
|
const reportTime = computed(
|
||||||
|
() => props.reportDateTime || new Date().toLocaleString('zh-CN'),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import './shared.scss';
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.report-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #8b4513;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-time {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-toolbar {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #8b4513;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn {
|
||||||
|
padding: 8px 18px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #8b4513;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--secondary {
|
||||||
|
background: #fff;
|
||||||
|
color: #8b4513;
|
||||||
|
border: 1px solid #d4af37;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.report-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
.no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-report {
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-card,
|
||||||
|
.panorama-report,
|
||||||
|
.intent-report {
|
||||||
|
break-inside: avoid;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
19
src/ui/DWBG9FB3/reportExport.js
Normal file
19
src/ui/DWBG9FB3/reportExport.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* 通过浏览器打印对话框导出 PDF(目标打印机选「另存为 PDF」)。
|
||||||
|
* 零依赖,适合当前纯前端报告查看器;复杂图表场景可改用服务端渲染 PDF。
|
||||||
|
*/
|
||||||
|
export async function printReportAsPdf(reportElement) {
|
||||||
|
if (!reportElement) return;
|
||||||
|
|
||||||
|
document.body.classList.add('dwbg9fb3-printing');
|
||||||
|
|
||||||
|
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.print();
|
||||||
|
} finally {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
document.body.classList.remove('dwbg9fb3-printing');
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
533
src/ui/DWBG9FB3/reportHelper.js
Normal file
533
src/ui/DWBG9FB3/reportHelper.js
Normal file
@@ -0,0 +1,533 @@
|
|||||||
|
/** DWBG9FB3 伽马报告工具函数 */
|
||||||
|
|
||||||
|
export const RISK_LEVEL_DESC = [
|
||||||
|
{ level: 'A', label: '优秀', desc: '未命中风险策略,建议通过' },
|
||||||
|
{ level: 'B', label: '未知', desc: '命中未知风险项,建议人工复核' },
|
||||||
|
{ level: 'C', label: '良好', desc: '命中中低风险策略,建议审核' },
|
||||||
|
{ level: 'D', label: '一般', desc: '命中中风险策略,建议严格审核' },
|
||||||
|
{ level: 'E', label: '较差', desc: '命中中高风险策略,建议拒绝' },
|
||||||
|
{ level: 'F', label: '极差', desc: '命中高风险策略,建议拒绝' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RISK_SUMMARY_CATEGORIES = [
|
||||||
|
{ key: 'mobile4Verify', title: '基本信息', icon: '📛' },
|
||||||
|
{ key: 'personalLawsuit', title: '司法案件', icon: '📑' },
|
||||||
|
{ key: 'loanRiskTagV11', title: '借贷意向', icon: '📈' },
|
||||||
|
{ key: 'loanRiskTagV10', title: '逾期勘测V3', icon: '📑' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const BLACKLIST_TAGS = [
|
||||||
|
{ key: 'black_tag04', label: '疑似短期频繁还款失败' },
|
||||||
|
{ key: 'black_tag05', label: '疑似短期频繁借贷' },
|
||||||
|
{ key: 'black_tag06', label: '疑似短期新机构频繁借贷' },
|
||||||
|
{ key: 'black_tag07', label: '疑似短期还款失败比例高' },
|
||||||
|
{ key: 'black_tag08', label: '疑似短期多机构逾期' },
|
||||||
|
{ key: 'black_tag09', label: '疑似中期频繁还款失败' },
|
||||||
|
{ key: 'black_tag10', label: '疑似短期新机构频繁还款失败' },
|
||||||
|
{ key: 'black_tag11', label: '疑似当前逾期较严重' },
|
||||||
|
{ key: 'black_tag12', label: '疑似高风险客户' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FRAUD_DIST_LABELS = ['银行', '保险', '消金', '汽车金融', '小贷', '特殊金融', '混合金融', '其他金融'];
|
||||||
|
|
||||||
|
export const FRAUD_DIST_KEYS = [
|
||||||
|
{ d30: 'h1_30d', d90: 'h1_90d', d180: 'h1_180d' },
|
||||||
|
{ d30: 'h2_30d', d90: 'h2_90d', d180: 'h2_180d' },
|
||||||
|
{ d30: 'h3_30d', d90: 'h3_90d', d180: 'h3_180d' },
|
||||||
|
{ d30: 'h4_30d', d90: 'h4_90d', d180: 'h4_180d' },
|
||||||
|
{ d30: 'h5_30d', d90: 'h5_90d', d180: 'h5_180d' },
|
||||||
|
{ d30: 'h6_30d', d90: 'h6_90d', d180: 'h6_180d' },
|
||||||
|
{ d30: 'h7_30d', d90: 'h7_90d', d180: 'h7_180d' },
|
||||||
|
{ d30: 'h8_30d', d90: 'h8_90d', d180: 'h8_180d' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OVERDUE_SURVEY_FIELDS = [
|
||||||
|
{ key: 'result_code', label: '探查结果', map: { 0: '正常', 1: '逾期未还款', 2: '逾期又还款' } },
|
||||||
|
{ key: 'max_overdue_amt', label: '最大逾期金额' },
|
||||||
|
{ key: 'max_overdue_days', label: '最长逾期天数' },
|
||||||
|
{ key: 'latest_overdue_time', label: '最近逾期时间' },
|
||||||
|
{ key: 'currently_performance', label: '最大履约金额' },
|
||||||
|
{ key: 'latest_performance_time', label: '最近履约时间' },
|
||||||
|
{ key: 'performance_count', label: '履约笔数' },
|
||||||
|
{ key: 'currently_overdue', label: '当前逾期机构数' },
|
||||||
|
{ key: 'currently_performance_org', label: '当前履约机构数' },
|
||||||
|
{ key: 'acc_exc', label: '异常还款机构数' },
|
||||||
|
{ key: 'acc_sleep', label: '睡眠机构数' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOAN_INTENT_PERIOD_PREFIXES = [
|
||||||
|
{ label: '近7天', prefix: 'd7' },
|
||||||
|
{ label: '近15天', prefix: 'd15' },
|
||||||
|
{ label: '近1个月', prefix: 'm1' },
|
||||||
|
{ label: '近3个月', prefix: 'm3' },
|
||||||
|
{ label: '近6个月', prefix: 'm6' },
|
||||||
|
{ label: '近12个月', prefix: 'm12' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 构建 als 字段名 */
|
||||||
|
export function alsField(prefix, dim, suffix, metric) {
|
||||||
|
return `als_${prefix}_${dim}_${suffix}_${metric}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 借贷意向 id/cell 成对展示,空值显示 -/- */
|
||||||
|
export function formatAlsPair(data, idKey, cellKey) {
|
||||||
|
if (!data || !idKey || !cellKey) return '-/-';
|
||||||
|
const hasId = data[idKey] !== null && data[idKey] !== undefined && data[idKey] !== '';
|
||||||
|
const hasCell = data[cellKey] !== null && data[cellKey] !== undefined && data[cellKey] !== '';
|
||||||
|
if (!hasId && !hasCell) return '-/-';
|
||||||
|
const id = hasId ? String(data[idKey]) : '-';
|
||||||
|
const cell = hasCell ? String(data[cellKey]) : '-';
|
||||||
|
return `${id}/${cell}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatAlsOrg(data, prefix, suffix) {
|
||||||
|
return formatAlsPair(
|
||||||
|
data,
|
||||||
|
alsField(prefix, 'id', suffix, 'orgnum'),
|
||||||
|
alsField(prefix, 'cell', suffix, 'orgnum'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatAlsCount(data, prefix, suffix) {
|
||||||
|
return formatAlsPair(
|
||||||
|
data,
|
||||||
|
alsField(prefix, 'id', suffix, 'allnum'),
|
||||||
|
alsField(prefix, 'cell', suffix, 'allnum'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LOAN_INTENT_INST_ROWS = [
|
||||||
|
{ label: '银行', suffix: 'bank' },
|
||||||
|
{ label: '非银', suffix: 'nbank' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOAN_INTENT_CUSTOMER_ROWS = [
|
||||||
|
{ label: '银行汇总', suffix: 'bank', highlight: true },
|
||||||
|
{ label: '传统银行', suffix: 'bank_tra' },
|
||||||
|
{ label: '网络零售银行', suffix: 'bank_ret' },
|
||||||
|
{ label: '非银汇总', suffix: 'nbank', highlight: true },
|
||||||
|
{ label: '持牌小贷', suffix: 'nbank_sloan' },
|
||||||
|
{ label: '持牌网络小贷', suffix: 'nbank_nsloan' },
|
||||||
|
{ label: '持牌消费金融', suffix: 'nbank_cons' },
|
||||||
|
{ label: '其他', suffix: 'nbank_oth' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOAN_INTENT_BUSINESS_ROWS = [
|
||||||
|
{ label: '信用卡(类信用卡)', suffix: 'rel' },
|
||||||
|
{ label: '线上现金分期', suffix: 'caon' },
|
||||||
|
{ label: '线下现金分期', suffix: 'caoff' },
|
||||||
|
{ label: '线上小额现金贷', suffix: 'pdl' },
|
||||||
|
{ label: '汽车金融', suffix: 'af' },
|
||||||
|
{ label: '线上消费分期', suffix: 'coon' },
|
||||||
|
{ label: '线下消费分期', suffix: 'cooff' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOAN_INTENT_ABNORMAL_ROWS = [
|
||||||
|
{ label: '夜间-银行', suffix: 'bank_night' },
|
||||||
|
{ label: '夜间-非银', suffix: 'nbank_nnight' },
|
||||||
|
{ label: '周末-银行', suffix: 'bank_week' },
|
||||||
|
{ label: '周末-非银', suffix: 'nbank_week' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOAN_INTENT_NOTE =
|
||||||
|
'注:<br>1. 取值结果展示:按身份证号查询命中次数/按手机号查询命中次数。如:"1/2" 表示按身份证号查询命中1次,按手机号查询命中2次。<br>2. 取值为 "-"、"0":无申请记录;';
|
||||||
|
|
||||||
|
export function cellText(v) {
|
||||||
|
if (v === null || v === undefined || v === '') return '—';
|
||||||
|
return String(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHit(v) {
|
||||||
|
return v === 1 || v === '1' || v === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPair(data, idKey, cellKey) {
|
||||||
|
if (!data) return '—';
|
||||||
|
const id = cellText(data[idKey]);
|
||||||
|
const cell = cellText(data[cellKey]);
|
||||||
|
if (id === '—' && cell === '—') return '—';
|
||||||
|
return `${id}/${cell}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function maskName(name) {
|
||||||
|
if (!name) return '—';
|
||||||
|
if (name.length <= 1) return '*';
|
||||||
|
if (name.length === 2) return `${name[0]}*`;
|
||||||
|
return `${name[0]}${'*'.repeat(name.length - 2)}${name[name.length - 1]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function maskIdCard(id) {
|
||||||
|
if (!id) return '—';
|
||||||
|
if (id.length <= 10) return id;
|
||||||
|
return `${id.slice(0, 6)}***********`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function maskMobile(mobile) {
|
||||||
|
if (!mobile || mobile.length !== 11) return cellText(mobile);
|
||||||
|
return `${mobile.slice(0, 3)}****${mobile.slice(7)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mobileStatusText(status) {
|
||||||
|
const map = { 1: '正常', 2: '停机', 3: '销号', 4: '空号' };
|
||||||
|
return map[status] ?? cellText(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fraudGradeText(grade) {
|
||||||
|
const map = { 1: '低风险', 2: '较低风险', 3: '中风险', 4: '较高风险', 5: '高风险' };
|
||||||
|
return map[grade] ?? '未命中';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function overdueResultText(code) {
|
||||||
|
const map = { 0: '正常', 1: '逾期未还款', 2: '逾期又还款' };
|
||||||
|
return map[code] ?? cellText(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function riskLevelColor(level) {
|
||||||
|
const map = { A: '#2ecc71', B: '#3498db', C: '#f39c12', D: '#e67e22', E: '#e74c3c', F: '#ff3333' };
|
||||||
|
return map[level] ?? '#999';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRoot(data) {
|
||||||
|
if (!data || typeof data !== 'object') return {};
|
||||||
|
if (data.result && typeof data.result === 'object') return data.result;
|
||||||
|
if (data.data?.result) return data.data.result;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从接口响应中提取查询人参数 */
|
||||||
|
export function extractReportParams(data) {
|
||||||
|
if (!data || typeof data !== 'object') return {};
|
||||||
|
if (data.reportParams && typeof data.reportParams === 'object') return data.reportParams;
|
||||||
|
if (data.params && typeof data.params === 'object') return data.params;
|
||||||
|
if (data.data?.reportParams) return data.data.reportParams;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从接口响应中提取报告输出时间 */
|
||||||
|
export function extractReportTime(data) {
|
||||||
|
if (!data || typeof data !== 'object') return '';
|
||||||
|
return data.reportDateTime || data.timestamp || data.data?.timestamp || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从接口响应中提取官方报告链接(若后端提供 PDF/在线报告) */
|
||||||
|
export function extractReportUrl(data) {
|
||||||
|
const root = parseRoot(data);
|
||||||
|
return root.reportUrl || data?.reportUrl || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 履约类指数(0-1)转为 0-1000 分展示 */
|
||||||
|
export function formatComplianceIndex(value) {
|
||||||
|
if (isEmptyXypValue(value)) {
|
||||||
|
return { value: '未命中', unit: '未命中' };
|
||||||
|
}
|
||||||
|
const n = parseFloat(value);
|
||||||
|
if (Number.isNaN(n)) {
|
||||||
|
return { value: '未命中', unit: '未命中' };
|
||||||
|
}
|
||||||
|
return { value: String(Math.round(n * 1000)), unit: '分' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据风险等级 F→A 渲染星级(越高风险越少亮星) */
|
||||||
|
export function riskLevelStars(level) {
|
||||||
|
const map = { A: 5, B: 4, C: 3, D: 2, E: 1, F: 0 };
|
||||||
|
const lit = map[level] ?? 0;
|
||||||
|
return '★'.repeat(lit) + '☆'.repeat(5 - lit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 司法风险项:优先 courtRisk 字段,被告未结案从案件统计推导 */
|
||||||
|
export function buildCourtRiskItems(courtRisk, personalLawsuit) {
|
||||||
|
const risk = courtRisk || {};
|
||||||
|
const count = personalLawsuit?.count || {};
|
||||||
|
const beigaoWei = risk.beigaoWei ?? (Number(count.count_wei_beigao) > 0);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{ key: 'zhixing', label: '是否被执行人员', hit: !!risk.zhixing },
|
||||||
|
{ key: 'xiangao', label: '是否限制高消费人员', hit: !!risk.xiangao },
|
||||||
|
{ key: 'shean', label: '是否涉案人员', hit: !!risk.shean },
|
||||||
|
{ key: 'beigao', label: '是否被告人员', hit: !!risk.beigao },
|
||||||
|
{ key: 'beigaoWei', label: '是否被告未结案人员', hit: !!beigaoWei },
|
||||||
|
{ key: 'xingshi', label: '是否命中刑事案件', hit: !!risk.xingshi },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 投诉意愿登记文案 */
|
||||||
|
export function dncRegisterText(dnc) {
|
||||||
|
if (dnc === null || dnc === undefined || dnc === '') return '未查得';
|
||||||
|
if (dnc === 0 || dnc === '0') return '未查得';
|
||||||
|
return String(dnc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析 "判决(2),维持(1)" 类分布字符串 */
|
||||||
|
export function parseStatDistribution(stat) {
|
||||||
|
if (!stat) return [];
|
||||||
|
return String(stat)
|
||||||
|
.split(',')
|
||||||
|
.map((part) => {
|
||||||
|
const m = part.trim().match(/^(.+)\((\d+)\)$/);
|
||||||
|
if (m) return { label: m[1].trim(), count: Number(m[2]) };
|
||||||
|
return { label: part.trim(), count: 1 };
|
||||||
|
})
|
||||||
|
.filter((item) => item.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
const PIE_COLORS = ['#4E74F6', '#89d079', '#f6c358', '#ff7c7c', '#9b59b6', '#5dade2'];
|
||||||
|
|
||||||
|
/** 根据分布数据生成环形图 conic-gradient */
|
||||||
|
export function buildConicGradient(items) {
|
||||||
|
if (!items.length) return '#e8eef7';
|
||||||
|
const total = items.reduce((s, i) => s + i.count, 0) || 1;
|
||||||
|
let acc = 0;
|
||||||
|
const stops = items.map((item, i) => {
|
||||||
|
const pct = (item.count / total) * 100;
|
||||||
|
const start = acc;
|
||||||
|
acc += pct;
|
||||||
|
return `${PIE_COLORS[i % PIE_COLORS.length]} ${start}% ${acc}%`;
|
||||||
|
});
|
||||||
|
return `conic-gradient(${stops.join(', ')})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toCaseArray(v) {
|
||||||
|
if (Array.isArray(v)) return v;
|
||||||
|
if (v && typeof v === 'object' && Object.keys(v).length) return [v];
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从司法数据中提取列表(兼容多种字段名) */
|
||||||
|
export function extractJudicialList(data, keys) {
|
||||||
|
if (!data) return [];
|
||||||
|
for (const key of keys) {
|
||||||
|
const list = toCaseArray(data[key]);
|
||||||
|
if (list.length) return list;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const REPORT_USAGE_NOTICE = [
|
||||||
|
'客户使用本报告,需经过被查询人授权,客户承担因授权不充分引起的任何法律责任。',
|
||||||
|
'本报告仅限客户内部使用,请妥善保管本报告,不得向任何第三方泄露或允许任何第三方使用本报告。',
|
||||||
|
'本报告仅供客户参考,不作为客户决策的依据。',
|
||||||
|
'未经我司书面许可,任何人不得擅自复制、摘录、编辑、转载、披露和发表。',
|
||||||
|
'请确保在安全的物理及网络环境操作并确保导出内容的保密、安全以及合规应用。',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function caseListText(c) {
|
||||||
|
if (!c) return '—';
|
||||||
|
return `${c.c_ah || '—'} 【${c.n_ssdw || '—'}】${c.n_ajjzjd || '—'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function limitHighRow(item) {
|
||||||
|
return {
|
||||||
|
ah: item.c_ah || item.ah || item.caseCode || '—',
|
||||||
|
legalPerson: item.legalPerson || item.qyfr || item.businessentity || '—',
|
||||||
|
entName: item.entName || item.qymc || item.companyName || '—',
|
||||||
|
court: item.n_jbfy || item.court || item.zxfy || '—',
|
||||||
|
publishDate: item.publishDate || item.fbrq || item.fb_date || '—',
|
||||||
|
filingDate: item.d_larq || item.larq || item.regDate || '—',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 模型指数 */
|
||||||
|
export const CREDIT_PANORAMA_SCORES = [
|
||||||
|
{ key: 'xyp_model_score_high', label: '小额网贷指数' },
|
||||||
|
{ key: 'xyp_model_score_mid', label: '小额分期指数' },
|
||||||
|
{ key: 'xyp_model_score_low', label: '中大额分期指数' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 机构借贷情况 */
|
||||||
|
export const CREDIT_PANORAMA_INSTITUTIONS = [
|
||||||
|
{ key: 'xyp_cpl0001', label: '贷款总机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0002', label: '贷款已结清机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0007', label: '消费金融类机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0008', label: '网络贷款类机构数', unit: '家' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 近期贷款申请机构数 */
|
||||||
|
export const CREDIT_PANORAMA_RECENT_LOAN = [
|
||||||
|
{ key: 'xyp_cpl0070', label: '最近1天贷款机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0009', label: '最近7天贷款机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0010', label: '最近14贷款机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0050', label: '最近21天贷款机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0011', label: '最近30天贷款机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0012', label: '最近90天贷款机构数', unit: '家' },
|
||||||
|
{ key: 'xyp_cpl0013', label: '最近180天贷款机构数', unit: '家' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 交易失败笔数 */
|
||||||
|
export const CREDIT_PANORAMA_FAIL_COUNTS = [
|
||||||
|
{ key: 'xyp_cpl0016', label: '最近1天交易失败笔数', unit: '笔' },
|
||||||
|
{ key: 'xyp_cpl0018', label: '最近7天交易失败笔数', unit: '笔' },
|
||||||
|
{ key: 'xyp_cpl0020', label: '最近14天交易失败笔数', unit: '笔' },
|
||||||
|
{ key: 'xyp_cpl0065', label: '最近21天交易失败笔数', unit: '笔' },
|
||||||
|
{ key: 'xyp_cpl0022', label: '最近30天交易失败笔数', unit: '笔' },
|
||||||
|
{ key: 'xyp_cpl0024', label: '最近90天交易失败笔数', unit: '笔' },
|
||||||
|
{ key: 'xyp_cpl0026', label: '最近180天交易失败笔数', unit: '笔' },
|
||||||
|
{ key: 'xyp_t03td148', label: '最近一次交易为交易失败机构数', unit: '家' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 交易失败后还款 */
|
||||||
|
export const CREDIT_PANORAMA_FAIL_RECOVERY = [
|
||||||
|
{ key: 'xyp_cpl0052', label: '消费金融类最后一次交易失败后还款次数', unit: '次' },
|
||||||
|
{ key: 'xyp_cpl0053', label: '小贷担保类最后一次交易失败后还款次数', unit: '次' },
|
||||||
|
{ key: 'xyp_cpl0069', label: '最后一次交易失败后还款次数', unit: '次' },
|
||||||
|
{ key: 'xyp_cpl0056', label: '交易失败向后距离下一次还款成功的天数最大值', unit: '天' },
|
||||||
|
{ key: 'xyp_cpl0062', label: '交易失败向后距离下一次还款成功的天数平均值', unit: '天' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 逾期标识 */
|
||||||
|
export const CREDIT_PANORAMA_OVERDUE_FLAGS = [
|
||||||
|
{ key: 'xyp_cpl0044', label: '当前是否存在逾期未结清', isFlag: true },
|
||||||
|
{ key: 'xyp_cpl0028', label: '最近1天是否发生过逾期', isFlag: true },
|
||||||
|
{ key: 'xyp_cpl0029', label: '最近7天是否发生过逾期', isFlag: true },
|
||||||
|
{ key: 'xyp_cpl0030', label: '最近14天是否发生过逾期', isFlag: true },
|
||||||
|
{ key: 'xyp_cpl0031', label: '最近30天是否发生过逾期', isFlag: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 贷款整体情况 */
|
||||||
|
export const CREDIT_PANORAMA_LOAN_OVERVIEW = [
|
||||||
|
{ key: 'xyp_cpl0045', label: '信用贷款时长', unit: '天', type: 'interval' },
|
||||||
|
{ key: 'xyp_cpl0046', label: '最近一次交易距离当前时间', unit: '天', type: 'interval' },
|
||||||
|
{ key: 'xyp_t01aczzzz', label: '累计交易金额', unit: '元', type: 'amount' },
|
||||||
|
{ key: 'xyp_cpl0058', label: '因交易能力不足导致失败的交易金额(最小值)', unit: '元', type: 'amount' },
|
||||||
|
{ key: 'xyp_cpl0014', label: '历史贷款机构成功还款笔数', unit: '笔', type: 'interval' },
|
||||||
|
{ key: 'xyp_cpl0015', label: '历史贷款机构交易失败笔数', unit: '笔', type: 'interval' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 信用全景扫描 — 分周期汇总表 */
|
||||||
|
export const CREDIT_PANORAMA_PERIOD_ROWS = [
|
||||||
|
{
|
||||||
|
label: '近1天',
|
||||||
|
instKey: 'xyp_cpl0070',
|
||||||
|
successKey: 'xyp_cpl0017',
|
||||||
|
successAmtKey: 'xyp_cpl0033',
|
||||||
|
failKey: 'xyp_cpl0016',
|
||||||
|
failAmtKey: 'xyp_cpl0032',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '近7天',
|
||||||
|
instKey: 'xyp_cpl0009',
|
||||||
|
successKey: 'xyp_cpl0019',
|
||||||
|
successAmtKey: 'xyp_cpl0035',
|
||||||
|
failKey: 'xyp_cpl0018',
|
||||||
|
failAmtKey: 'xyp_cpl0034',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '近14天',
|
||||||
|
instKey: 'xyp_cpl0010',
|
||||||
|
successKey: 'xyp_cpl0021',
|
||||||
|
successAmtKey: 'xyp_cpl0037',
|
||||||
|
failKey: 'xyp_cpl0020',
|
||||||
|
failAmtKey: 'xyp_cpl0036',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '近21天',
|
||||||
|
instKey: 'xyp_cpl0050',
|
||||||
|
successKey: 'xyp_cpl0064',
|
||||||
|
successAmtKey: 'xyp_cpl0067',
|
||||||
|
failKey: 'xyp_cpl0065',
|
||||||
|
failAmtKey: 'xyp_cpl0066',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '近30天',
|
||||||
|
instKey: 'xyp_cpl0011',
|
||||||
|
successKey: 'xyp_cpl0023',
|
||||||
|
successAmtKey: 'xyp_cpl0039',
|
||||||
|
failKey: 'xyp_cpl0022',
|
||||||
|
failAmtKey: 'xyp_cpl0038',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '近90天',
|
||||||
|
instKey: 'xyp_cpl0012',
|
||||||
|
successKey: 'xyp_cpl0025',
|
||||||
|
successAmtKey: 'xyp_cpl0041',
|
||||||
|
failKey: 'xyp_cpl0024',
|
||||||
|
failAmtKey: 'xyp_cpl0040',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '近180天',
|
||||||
|
instKey: 'xyp_cpl0013',
|
||||||
|
successKey: 'xyp_cpl0027',
|
||||||
|
successAmtKey: 'xyp_cpl0043',
|
||||||
|
failKey: 'xyp_cpl0026',
|
||||||
|
failAmtKey: 'xyp_cpl0042',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function isEmptyXypValue(v) {
|
||||||
|
return v === null || v === undefined || v === '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析 xyp 区间码为展示数值,空值返回 null */
|
||||||
|
export function parseXypInterval(value) {
|
||||||
|
if (isEmptyXypValue(value)) return null;
|
||||||
|
const num = parseInt(value, 10);
|
||||||
|
if (Number.isNaN(num)) return null;
|
||||||
|
switch (num) {
|
||||||
|
case 1: return 1;
|
||||||
|
case 2: return 3;
|
||||||
|
case 3: return 7;
|
||||||
|
case 4: return 15;
|
||||||
|
case 5: return 25;
|
||||||
|
default: return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPanoramaCount(value, unit = '') {
|
||||||
|
if (isEmptyXypValue(value)) return '-';
|
||||||
|
const n = parseXypInterval(value);
|
||||||
|
if (n === null) return '-';
|
||||||
|
if (n === 0) return `0${unit}`;
|
||||||
|
if (n < 5) return `${n}${unit}`;
|
||||||
|
return `${n}+${unit}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPanoramaAmount(value) {
|
||||||
|
if (isEmptyXypValue(value)) return '-';
|
||||||
|
const n = parseXypInterval(value);
|
||||||
|
if (n === null) return '-';
|
||||||
|
if (n === 0) return '0元';
|
||||||
|
if (n < 1000) return `${n}元`;
|
||||||
|
if (n < 10000) return `${(n / 1000).toFixed(1)}千元`;
|
||||||
|
return `${(n / 10000).toFixed(1)}万元`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPanoramaRatio(value) {
|
||||||
|
if (isEmptyXypValue(value)) return '-';
|
||||||
|
const n = parseFloat(value);
|
||||||
|
if (Number.isNaN(n)) return '-';
|
||||||
|
return `${(n * 100).toFixed(1)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPanoramaYesNo(value) {
|
||||||
|
if (isEmptyXypValue(value)) return '-';
|
||||||
|
return value === '1' || value === 1 ? '是' : '否';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatModelScore(value) {
|
||||||
|
if (isEmptyXypValue(value)) return '未命中';
|
||||||
|
const n = parseInt(value, 10);
|
||||||
|
if (Number.isNaN(n)) return '未命中';
|
||||||
|
return String(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPanoramaRiskTag(value, { isFlag = false } = {}) {
|
||||||
|
if (isEmptyXypValue(value)) {
|
||||||
|
return { label: '低风险', danger: false, warn: false };
|
||||||
|
}
|
||||||
|
if (isFlag) {
|
||||||
|
const hit = value === '1' || value === 1;
|
||||||
|
return hit
|
||||||
|
? { label: '高风险', danger: true, warn: false }
|
||||||
|
: { label: '低风险', danger: false, warn: false };
|
||||||
|
}
|
||||||
|
const n = parseXypInterval(value) ?? 0;
|
||||||
|
if (n >= 15) return { label: '高风险', danger: true, warn: false };
|
||||||
|
if (n >= 7) return { label: '中风险', danger: false, warn: true };
|
||||||
|
return { label: '低风险', danger: false, warn: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function panoramaTableCell(data, key, type = 'count') {
|
||||||
|
if (!data || !key) return '-';
|
||||||
|
const v = data[key];
|
||||||
|
if (type === 'amount') return formatPanoramaAmount(v);
|
||||||
|
return formatPanoramaCount(v, type === 'inst' ? '' : '');
|
||||||
|
}
|
||||||
133
src/ui/DWBG9FB3/shared.scss
Normal file
133
src/ui/DWBG9FB3/shared.scss
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
.gamma-report {
|
||||||
|
--gamma-brown: #8b4513;
|
||||||
|
--gamma-danger: #d93025;
|
||||||
|
--gamma-success: #2ecc71;
|
||||||
|
--gamma-bg: #f5f7fa;
|
||||||
|
background: var(--gamma-bg);
|
||||||
|
padding: 20px;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-subtitle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin: 16px 0 12px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 2px solid #f0e6d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-grid-2 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-grid-3 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-grid-4 {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 10px;
|
||||||
|
background: #fafbfc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-item label {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #e6f7e6;
|
||||||
|
color: #3cb371;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-tag--danger {
|
||||||
|
background: #fef0f0;
|
||||||
|
color: var(--gamma-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-tag--warn {
|
||||||
|
background: #fff7e6;
|
||||||
|
color: #d89614;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #fff8e6;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-text-danger { color: var(--gamma-danger); }
|
||||||
|
.gamma-text-success { color: var(--gamma-success); }
|
||||||
|
.gamma-small { font-size: 12px; color: #999; }
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.gamma-grid-2,
|
||||||
|
.gamma-grid-3,
|
||||||
|
.gamma-grid-4 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/views/DWBG9FB3Report.vue
Normal file
95
src/views/DWBG9FB3Report.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<DWBG9FB3Report
|
||||||
|
v-if="isDone"
|
||||||
|
:data="reportData"
|
||||||
|
:params="reportParams"
|
||||||
|
:report-date-time="reportDateTime"
|
||||||
|
:show-toolbar="true"
|
||||||
|
/>
|
||||||
|
<div v-else class="loading-container">
|
||||||
|
<div class="loading-spinner" />
|
||||||
|
<p>加载中,请稍候...</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import DWBG9FB3Report from '@/ui/DWBG9FB3/index.vue';
|
||||||
|
import { extractReportParams, extractReportTime } from '@/ui/DWBG9FB3/reportHelper';
|
||||||
|
|
||||||
|
const reportData = ref({});
|
||||||
|
const reportParams = ref({});
|
||||||
|
const reportDateTime = ref('');
|
||||||
|
const isDone = ref(false);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/DWBG9FB3.json');
|
||||||
|
const json = await response.json();
|
||||||
|
reportData.value = json;
|
||||||
|
reportParams.value = extractReportParams(json);
|
||||||
|
reportDateTime.value =
|
||||||
|
extractReportTime(json) || new Date().toLocaleString('zh-CN');
|
||||||
|
isDone.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[DWBG9FB3Report] 加载示例数据失败:', error);
|
||||||
|
isDone.value = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #8b4513;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p { color: #666; font-size: 16px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 10mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dwbg9fb3-printing {
|
||||||
|
background: #fff !important;
|
||||||
|
|
||||||
|
#app {
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-report {
|
||||||
|
padding: 0 !important;
|
||||||
|
background: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gamma-container {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user