Files
report_viewer/src/ui/CJRZQ4AA8.vue
2025-12-18 15:39:43 +08:00

370 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { computed, ref, onMounted, onUnmounted, watch } from "vue";
import * as echarts from "echarts";
import { useRiskNotifier } from "@/composables/useRiskNotifier";
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
});
// 计算得分如果没有数据则默认为0
const score = computed(() => {
return props.data?.score ? Number(props.data.score) : 0;
});
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
// 还款压力分数越高风险越大,转换为安全分数
// 压力分数 0-20100分最安全
// 压力分数 20-5070分较安全
// 压力分数 50-8040分有风险
// 压力分数 80-10010分高风险
const pressure = score.value;
if (pressure <= 20) return 100;
if (pressure <= 50) return 70;
if (pressure <= 80) return 40;
return 10;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
// 根据分值确定压力等级
const pressureLevel = computed(() => {
if (score.value <= 20)
return {
level: "低",
color: "#67C23A",
text: "还款压力小",
bgGradient: "from-green-500 to-green-300",
lightBg: "bg-green-50",
borderColor: "border-green-200",
gradient: [
{ offset: 0, color: "#67C23A" },
{ offset: 1, color: "#85ce61" }
]
};
if (score.value <= 50)
return {
level: "中",
color: "#E6A23C",
text: "还款压力中等",
bgGradient: "from-yellow-500 to-yellow-300",
lightBg: "bg-yellow-50",
borderColor: "border-yellow-200",
gradient: [
{ offset: 0, color: "#E6A23C" },
{ offset: 1, color: "#ebb563" }
]
};
if (score.value <= 80)
return {
level: "高",
color: "#E53E3E",
text: "还款压力较大",
bgGradient: "from-orange-500 to-red-400",
lightBg: "bg-red-50",
borderColor: "border-red-200",
gradient: [
{ offset: 0, color: "#E53E3E" },
{ offset: 1, color: "#fc8181" }
]
};
return {
level: "极高",
color: "#FF0000",
text: "还款压力非常大",
bgGradient: "from-red-600 to-red-500",
lightBg: "bg-red-50",
borderColor: "border-red-300",
gradient: [
{ offset: 0, color: "#FF0000" },
{ offset: 1, color: "#ff3333" }
]
};
});
// 计算进度条宽度百分比
const progressWidth = computed(() => {
return `${score.value}%`;
});
// 计算评分对应的Tailwind文本颜色类
const scoreColorClass = computed(() => {
if (score.value <= 20) return "text-green-500";
if (score.value <= 50) return "text-yellow-500";
if (score.value <= 80) return "text-orange-500";
return "text-red-600";
});
// 获取图标路径(根据压力等级)
const getIconPath = () => {
// 低压力使用 zq
if (score.value <= 20) {
return new URL('@/assets/images/report/zq.png', import.meta.url).href
}
// 中等压力使用 zfx
if (score.value <= 50) {
return new URL('@/assets/images/report/zfx.png', import.meta.url).href
}
// 高压力和极高压力使用 gfx
return new URL('@/assets/images/report/gfx.png', import.meta.url).href
};
// 获取边框颜色
const getBorderColor = () => {
if (score.value <= 20) return '#bbf7d0'; // 绿色
if (score.value <= 50) return '#fef3c7'; // 黄色
if (score.value <= 80) return '#fecaca'; // 红色
return '#fecaca'; // 极高压力也是红色
};
// ECharts 仪表盘
const chartRef = ref(null);
let chartInstance = null;
const initChart = () => {
if (!chartRef.value) return;
chartInstance = echarts.init(chartRef.value);
updateChart();
};
const updateChart = () => {
if (!chartInstance) return;
const risk = pressureLevel.value;
const option = {
series: [
{
type: "gauge",
startAngle: 180,
endAngle: 0,
min: 0,
max: 100,
radius: "100%",
center: ["50%", "80%"],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, risk.gradient),
shadowBlur: 6,
shadowColor: risk.color,
},
progress: {
show: true,
width: 20,
roundCap: true,
clip: false
},
axisLine: {
roundCap: true,
lineStyle: {
width: 20,
color: [
[1, new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: risk.color + "30"
},
{
offset: 1,
color: risk.color + "25"
}
])]
]
}
},
axisTick: {
show: true,
distance: -30,
length: 6,
splitNumber: 10,
lineStyle: {
color: risk.color,
width: 1,
opacity: 0.5
}
},
splitLine: {
show: true,
distance: -36,
length: 12,
splitNumber: 9,
lineStyle: {
color: risk.color,
width: 2,
opacity: 0.5
}
},
axisLabel: {
show: false,
},
anchor: {
show: false
},
pointer: {
icon: "triangle",
iconStyle: {
color: risk.color,
borderColor: risk.color,
borderWidth: 1
},
offsetCenter: ["7%", "-67%"],
length: "10%",
width: 15
},
detail: {
valueAnimation: true,
fontSize: 30,
fontWeight: "bold",
color: risk.color,
offsetCenter: [0, "-25%"],
formatter: function (value) {
return `{value|${value}分}\n{level|${risk.level}级还款压力}`;
},
rich: {
value: {
fontSize: 30,
fontWeight: 'bold',
color: risk.color,
padding: [0, 0, 5, 0]
},
level: {
fontSize: 14,
fontWeight: 'normal',
color: risk.color,
padding: [5, 0, 0, 0]
}
}
},
data: [
{
value: score.value
}
],
title: {
fontSize: 14,
color: risk.color,
offsetCenter: [0, "10%"],
formatter: risk.level + "级还款压力"
}
}
]
};
chartInstance.setOption(option);
};
watch(
() => score.value,
() => {
updateChart();
}
);
onMounted(() => {
initChart();
window.addEventListener("resize", () => {
if (chartInstance) {
chartInstance.resize();
}
});
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener("resize", chartInstance?.resize);
});
</script>
<template>
<div class="card">
<div class="rounded-lg border border-gray-200 pb-2 mb-4">
<!-- 标题栏 -->
<div class="flex items-center mb-4 p-4">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/hkylfx.png" alt="还款压力分析" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">还款压力分析</span>
</div>
<div class="px-4 pb-4">
<!-- 仪表盘图表 -->
<div class="mb-6">
<div ref="chartRef" :style="{ width: '100%', height: '200px' }"></div>
</div>
<!-- 压力等级显示 -->
<div class="mb-6">
<div class="space-y-3 p-4 rounded-lg border" :class="pressureLevel.lightBg"
:style="{ borderColor: getBorderColor() }">
<div class="flex items-start">
<div class="mr-3 mt-1">
<img :src="getIconPath()" alt="还款压力" class="w-10 h-10 object-contain" />
</div>
<div class="flex-1">
<h4 class="font-semibold text-gray-800 mb-2">{{ pressureLevel.text }}</h4>
<p class="text-gray-400 text-sm">
分值越高表示还款压力越大建议关注债务比例
</p>
</div>
</div>
</div>
</div>
<!-- 财务建议 -->
<div class="mb-6">
<div class="flex items-center mb-3">
<div class="w-4 h-4 flex items-center justify-center mr-2">
<img src="@/assets/images/report/wxts_icon.png" alt="财务建议" class="w-4 h-4 object-contain" />
</div>
<div class="font-bold text-gray-800">财务建议</div>
</div>
<div class="ml-6 text-sm text-gray-600 space-y-1">
<p v-if="score > 50">
建议合理规划财务控制债务比例增加收入来源避免过度负债
</p>
<p v-if="score > 50" class="mt-1">
可尝试分期付款或延长还款周期减轻每月还款压力
</p>
<p v-else>
当前还款压力在可控范围内继续保持良好的财务习惯
</p>
<p v-if="score <= 50" class="mt-1">
建议定期检查收支平衡确保及时还款维持良好信用记录
</p>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* 样式已通过 Tailwind CSS 类实现 */
</style>