220 lines
5.8 KiB
Vue
220 lines
5.8 KiB
Vue
<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>
|