add
This commit is contained in:
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>
|
||||
Reference in New Issue
Block a user