f
This commit is contained in:
96
src/ui/9B3F-在线示例留存/components/BasicInfoSection.vue
Normal file
96
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/ComplaintRiskSection.vue
Normal file
87
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/CreditPanoramaSection.vue
Normal file
462
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/FraudBlacklistSection.vue
Normal file
219
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/JudicialCaseSection.vue
Normal file
984
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/LoanIntentSection.vue
Normal file
278
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/LoanProfileSection.vue
Normal file
80
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/OverdueBlacklistSection.vue
Normal file
83
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/OverdueSurveySection.vue
Normal file
28
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/RiskAssessmentSection.vue
Normal file
148
src/ui/9B3F-在线示例留存/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/9B3F-在线示例留存/components/RiskSummarySection.vue
Normal file
70
src/ui/9B3F-在线示例留存/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>
|
||||
Reference in New Issue
Block a user