This commit is contained in:
Mrx
2026-04-19 16:30:06 +08:00
parent 7b472db9d8
commit 5c4921b34e
10 changed files with 788 additions and 26 deletions

View File

@@ -95,7 +95,13 @@
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
<script> <script>
console.log('[index.html] 页面脚本开始执行');
window.onerror = function(msg, url, lineNo, columnNo, error) {
console.error('[index.html] 全局错误捕捉:', msg, 'at', url, 'line:', lineNo);
return false;
};
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
console.log('[index.html] DOMContentLoaded 已触发');
const loadingElement = document.getElementById('app-loading'); const loadingElement = document.getElementById('app-loading');
if (loadingElement) { if (loadingElement) {
loadingElement.style.opacity = '0'; loadingElement.style.opacity = '0';

View File

@@ -10532,6 +10532,70 @@
] ]
} }
}, },
{
"feature": {
"featureName": "法院被执行人高级版",
"sort": 1
},
"data": {
"apiID": "FLXGK5D2",
"data": {
"el_sx1_datatype": "失信被执行人",
"el_sx2_casecode": "2023皖0403执3847号",
"el_xg1_sexname": "男",
"el_sx1_iname": "xxx",
"el_sx2_gistcid": "2023皖0403民初1229号",
"el_sx2_age": "32",
"el_sx2_signalRating": "1",
"el_xg2_regdate": "2023-10-07",
"el_xg1_areaname": "广东省",
"el_sx1_regdate": "2024-03-07",
"el_sx1_publishdate": "2024-03-11",
"el_xg1_signalDesc": "主体被曝光,且发生在三年之内",
"el_sx2_publishdate": "2023-11-06",
"Rule_name_odr0000210": "为法院失信被执行人",
"el_sx1_sexname": "男性",
"Rule_final_decision": "Reject",
"el_sx2_iname": "xxx",
"el_sx1_casecode": "2024粤0106执5293号",
"el_sx1_partytypename": "0",
"el_sx1_signalDesc": "主体被拉入失信名单,且发生在三年之内",
"Rule_final_weight": "80",
"el_xg1_age": "33",
"el_sx2_datatype": "失信被执行人",
"el_xg2_casecode": "2023皖0403执3847号",
"el_sx2_courtname": "安徽省淮南市田家庵区人民法院",
"el_sx2_signalDesc": "主体被拉入失信名单,且发生在三年之内",
"el_xg2_areaname": "安徽省",
"el_xg2_sexname": "男",
"el_xg1_datatype": "限高被执行人",
"el_sx2_partytypename": "0",
"el_sx1_signalRating": "1",
"el_xg2_courtname": "安徽省淮南市田家庵区人民法院",
"el_xg2_iname": "xxx",
"el_xg2_age": "32",
"el_sx1_areaname": "广东省",
"el_xg1_iname": "xxx",
"el_sx2_sexname": "男性",
"el_xg1_regdate": "2024-03-07",
"el_xg1_publishdate": "2024-03-11",
"el_sx2_regdate": "2023-10-07",
"el_xg2_publishdate": "2023-10-24",
"el_xg2_datatype": "限高被执行人",
"Rule_weight_odr0000210": "80",
"el_xg1_courtname": "广东省广州市天河区人民法院",
"el_xg2_signalDesc": "主体被曝光,且发生在三年之内,但已下架",
"el_sx1_age": "33",
"el_sx1_gistcid": "2023粤0106民初22456等号",
"el_sx1_courtname": "广东省广州市天河区人民法院",
"el_xg1_casecode": "2024粤0106执5293号",
"el_sx2_areaname": "安徽省",
"el_xg2_signalRating": "2",
"el_xg2_sign": "0",
"el_xg1_signalRating": "1"
}
}
},
{ {
"feature": { "feature": {
"featureName": "劳动仲裁信息查询(个人版)", "featureName": "劳动仲裁信息查询(个人版)",

View File

@@ -87,24 +87,6 @@ onMounted(async () => {
await loadTrapezoidBackground(); await loadTrapezoidBackground();
}); });
// 处理数据拆分支持DWBG8B4D、DWBG6A2C、CJRZQ5E9F和CQYGL3F8E
const processedReportData = computed(() => {
let data = reportData.value;
// 拆分DWBG8B4D数据
data = splitDWBG8B4DForTabs(data);
// 拆分DWBG6A2C数据
data = splitDWBG6A2CForTabs(data);
// 拆分CQYGL3F8E数据
data = splitCQYGL3F8EForTabs(data);
// 过滤掉在featureMap中没有对应的项
return data.filter(item => featureMap[item.data.apiID]);
});
// 牌匾背景图片样式
const trapezoidBgStyle = computed(() => { const trapezoidBgStyle = computed(() => {
if (trapezoidBgImage.value) { if (trapezoidBgImage.value) {
return { return {
@@ -183,6 +165,15 @@ const featureMap = {
), ),
}, },
// 法院被执行人高级版
FLXGK5D2: {
name: "法院被执行人高级版",
component: defineAsyncComponent(() =>
import("@/ui/FLXGK5D2/index.vue")
),
remark: '法院被执行人高级版展示申请人作为失信被执行人或限高被执行人的详细情况。数据来源于法院公开信息,包括案号、执行法院、立案时间及风险信号描述等。'
},
// 个人涉诉 // 个人涉诉
FLXG7E8F: { FLXG7E8F: {
name: "个人涉诉", name: "个人涉诉",
@@ -492,6 +483,7 @@ const maskValue = computed(() => {
const featureRiskLevels = { const featureRiskLevels = {
// 🔴 高风险类 // 🔴 高风险类
'FLXG0V4B': 20, // 司法涉诉 'FLXG0V4B': 20, // 司法涉诉
'FLXGK5D2': 20, // 法院被执行人高级版
'FLXG7E8F': 20, // 个人涉诉 'FLXG7E8F': 20, // 个人涉诉
// 🟠 中高风险类 - 权重 7 // 🟠 中高风险类 - 权重 7
@@ -511,7 +503,7 @@ const featureRiskLevels = {
// 🟡 中风险类 - 权重 5 // 🟡 中风险类 - 权重 5
'QYGL3F8E': 5, // 人企关系加强版 'QYGL3F8E': 5, // 人企关系加强版
'QCXG9P1C': 5, // 名下车辆 'QCXG9P1C': 5, // 名下车辆贷前
'QCXG7A2B': 3, // 名下车辆(简化版) 'QCXG7A2B': 3, // 名下车辆(简化版)
'JRZQ09J8': 5, // 收入评估 'JRZQ09J8': 5, // 收入评估
'JRZQ8B3C': 5, // 个人消费能力等级 'JRZQ8B3C': 5, // 个人消费能力等级
@@ -537,7 +529,7 @@ const featureRiskLevels = {
'DWBG8B4D_LeasingRisk': 6, 'DWBG8B4D_LeasingRisk': 6,
'DWBG8B4D_RiskSupervision': 8, 'DWBG8B4D_RiskSupervision': 8,
'DWBG8B4D_RiskWarningTab': 9, 'DWBG8B4D_RiskWarningTab': 9,
// 'DWBG6A2C_CourtRiskInfo':9, 'DWBG6A2C_CourtRiskInfo':9,
// 司南报告子模块 // 司南报告子模块
@@ -566,6 +558,32 @@ const featureRiskLevels = {
'CQYGL3F8E_TaxRisk': 7, 'CQYGL3F8E_TaxRisk': 7,
}; };
// 处理数据拆分支持DWBG8B4D、DWBG6A2C、CJRZQ5E9F和CQYGL3F8E
const processedReportData = computed(() => {
console.log('[BaseReport.vue] 开始计算 processedReportData...');
let data = reportData.value || [];
// 拆分DWBG8B4D数据
data = splitDWBG8B4DForTabs(data);
// 拆分DWBG6A2C数据
data = splitDWBG6A2CForTabs(data);
// 拆分CQYGL3F8E数据
data = splitCQYGL3F8EForTabs(data);
// 过滤掉在featureMap中没有对应的项
const filtered = data.filter(item => {
const apiID = item?.data?.apiID;
const exists = item && item.data && apiID && featureMap[apiID];
if (!exists && item?.data?.apiID) {
console.warn(`[BaseReport.vue] 未找到 API ID "${apiID}" 的对应组件配置,已过滤。`);
}
return exists;
});
console.log('[BaseReport.vue] 过滤后模块数量:', filtered.length);
return filtered;
});
// 存储每个组件的 ref 引用 // 存储每个组件的 ref 引用
const componentRefs = ref({}); const componentRefs = ref({});
@@ -585,6 +603,7 @@ defineExpose({
// 计算综合评分的函数(分数越高越安全) // 计算综合评分的函数(分数越高越安全)
const calculateScore = () => { const calculateScore = () => {
console.log('[BaseReport.vue] 开始计算报告评分...');
// 收集实际存在的 features 及其风险权重 // 收集实际存在的 features 及其风险权重
const presentFeatures = []; const presentFeatures = [];
@@ -605,6 +624,7 @@ const calculateScore = () => {
}); });
}); });
console.log('[BaseReport.vue] 参与评分的特征数量:', presentFeatures.length);
if (presentFeatures.length === 0) return 100; // 无有效特征时返回满分(最安全) if (presentFeatures.length === 0) return 100; // 无有效特征时返回满分(最安全)
// 累计总风险分数 // 累计总风险分数
@@ -641,12 +661,14 @@ const calculateScore = () => {
totalRiskScore += riskContribution; totalRiskScore += riskContribution;
}); });
console.log('[BaseReport.vue] 计算得出的总风险分数:', totalRiskScore);
// 将总风险分数限制在 0-90 范围内确保最低分为10分 // 将总风险分数限制在 0-90 范围内确保最低分为10分
const finalRiskScore = Math.max(0, Math.min(90, Math.round(totalRiskScore))); const finalRiskScore = Math.max(0, Math.min(90, Math.round(totalRiskScore)));
// 转换为安全分数分数越高越安全100 - 风险分数) // 转换为安全分数分数越高越安全100 - 风险分数)
// 最终分数范围10-100分 // 最终分数范围10-100分
const safetyScore = 100 - finalRiskScore; const safetyScore = 100 - finalRiskScore;
console.log('[BaseReport.vue] 最终安全评分:', safetyScore);
return safetyScore; return safetyScore;
}; };
@@ -660,11 +682,11 @@ watch([reportData, componentRiskScores], () => {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
finalScore: reportScore.value, finalScore: reportScore.value,
reportModules: processedReportData.value.map((item, index) => ({ reportModules: processedReportData.value.map((item, index) => ({
apiID: item.data.apiID, apiID: item?.data?.apiID || 'unknown',
name: featureMap[item.data.apiID]?.name || '未知', name: featureMap[item?.data?.apiID]?.name || '未知',
index: index, index: index,
riskScore: componentRiskScores.value[`${item.data.apiID}_${index}`] ?? '未上报', riskScore: componentRiskScores.value[`${item?.data?.apiID}_${index}`] ?? '未上报',
weight: featureRiskLevels[item.data.apiID] ?? 0 weight: featureRiskLevels[item?.data?.apiID] ?? 0
})), })),
componentScores: componentRiskScores.value, componentScores: componentRiskScores.value,
riskLevels: featureRiskLevels riskLevels: featureRiskLevels

View File

@@ -1,15 +1,21 @@
console.log('[main.js] 脚本开始执行...');
import "./assets/main.css"; import "./assets/main.css";
import { createApp } from "vue"; import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import App from "./App.vue"; import App from "./App.vue";
console.log('[main.js] 依赖库导入完成');
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
routes: [ routes: [
{ {
path: "/", path: "/",
name: "Report", name: "Report",
component: () => import("./views/Report.vue"), component: () => {
console.log('[main.js] 路由正在加载 Report.vue...');
return import("./views/Report.vue");
},
}, },
], ],
}); });
@@ -17,4 +23,6 @@ const router = createRouter({
const app = createApp(App); const app = createApp(App);
app.use(router); app.use(router);
console.log('[main.js] 准备挂载应用...');
app.mount("#app"); app.mount("#app");
console.log('[main.js] 应用挂载指令已发出');

BIN
src/ui/FLXGK5D2.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,76 @@
<template>
<div class="px-4 pb-4">
<div class="grid grid-cols-[max-content_1fr] gap-x-2 gap-y-3">
<!-- 执行法院 -->
<span class="text-base text-[#666666]">执行法院</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.courtname || "—" }}</span>
<!-- 所属地域 -->
<span class="text-base text-[#666666]">所属地域</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.areaname || "—" }}</span>
<!-- 立案时间 -->
<template v-if="caseData.regdate">
<span class="text-base text-[#666666]">立案时间</span>
<span class="text-base font-medium text-[#333333]">{{ formatDate(caseData.regdate) }}</span>
</template>
<!-- 发布时间 -->
<template v-if="caseData.publishdate">
<span class="text-base text-[#666666]">发布时间</span>
<span class="text-base font-medium text-[#333333]">{{ formatDate(caseData.publishdate) }}</span>
</template>
<!-- 性别 -->
<template v-if="caseData.sexname">
<span class="text-base text-[#666666]">性别</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.sexname || "—" }}</span>
</template>
<!-- 人员/企业类型 -->
<template v-if="caseData.partytypename !== undefined">
<span class="text-base text-[#666666]">主体类型</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.partytypename === '0' ? '自然人' : (caseData.partytypename === '1' ? '企业' : '未知') }}</span>
</template>
<!-- 年龄 -->
<template v-if="caseData.age">
<span class="text-base text-[#666666]">年龄</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.age || "—" }}</span>
</template>
<!-- 执行依据文号 -->
<template v-if="caseData.gistcid">
<span class="text-base text-[#666666]">执行依据文号</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.gistcid || "—" }}</span>
</template>
<!-- 信号描述 -->
<template v-if="caseData.signalDesc">
<span class="text-base text-[#666666]">信号描述</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.signalDesc || "—" }}</span>
</template>
<!-- 信号等级 -->
<template v-if="caseData.signalRating">
<span class="text-base text-[#666666]">信号等级</span>
<span class="text-base font-medium text-[#333333]">{{ caseData.signalRating || "—" }} (1为最高, 7为最低)</span>
</template>
</div>
</div>
</template>
<script setup>
import { formatDate } from '../utils/lawsuitUtils.js'
const props = defineProps({
caseData: {
type: Object,
required: true
},
caseType: {
type: String,
required: true
}
})
</script>

View File

@@ -0,0 +1,93 @@
<template>
<div class="">
<!-- 概览标题 -->
<div class="p-4">
<!-- 风险概览总结 -->
<div class="p-4 rounded-lg" :class="getRiskOverviewClass()">
<div class="flex items-center">
<div class="w-12 h-12 mr-3 flex-shrink-0">
<img :src="getRiskIcon()" alt="风险" class="w-12 h-12 object-contain" />
</div>
<div class="text-gray-700">
{{ totalCases }}
起涉诉案件中
<span v-if="stats.highRiskItems > 0" class="text-orange-600 font-medium">
{{ stats.highRiskItems }}
</span>
<span v-else class="text-green-600 font-medium">0</span>
起高风险案件
<span v-if="stats.caseTypes.length > 0" class="ml-1">
涉及 {{ stats.caseTypes.length }} 种案件类型
</span>
</div>
</div>
</div>
</div>
<!-- 主要风险指标 -->
<div class="grid grid-cols-2 gap-3 p-4">
<!-- 风险事项卡片 -->
<div class="p-4 bg-[#EB3C3C1A] border border-[#EB3C3C4D] rounded-xl text-center">
<div class="text-2xl font-bold text-[#EB3C3C] mb-1">{{ stats.totalRiskItems || 0 }}</div>
<div class="text-sm font-medium text-gray-800 mb-1">风险事项</div>
<div class="text-xs text-gray-500">
命中{{ stats.totalRiskItems || 0 }}个规则
</div>
</div>
<!-- 高风险案件卡片 -->
<div class="p-4 bg-[#EB3C3C1A] border border-[#EB3C3C4D] rounded-xl text-center">
<div class="text-2xl font-bold text-[#EB3C3C] mb-1">{{ stats.highRiskItems || 0 }}</div>
<div class="text-sm font-medium text-gray-800 mb-1">高风险案件</div>
<div class="text-xs text-orange-600">
<span class="mr-3">失信{{ stats.sxbzxrCount || 0 }}</span>
<span>限高{{ stats.xgbzxrCount || 0 }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
const props = defineProps({
stats: {
type: Object,
required: true,
},
totalCases: {
type: Number,
required: true,
},
})
// 获取风险概览样式
const getRiskOverviewClass = () => {
// 有高风险案件 - 红色警告
if (props.stats.highRiskItems > 0) {
return 'bg-[#F9ECEC] border border-[#F0CACA]'
}
// 有案件但无高风险 - 黄色警示
if (props.totalCases > 0) {
return 'bg-[#FFF8E1] border border-[#FFE082]'
}
// 无案件 - 绿色正常
return 'bg-[#ECF9EF] border border-[#CAECD3]'
}
// 获取风险图标
const getRiskIcon = () => {
// 有高风险案件 - 高风险图标
if (props.stats.highRiskItems > 0) {
return new URL('../../../assets/images/report/gfx.png', import.meta.url).href
}
// 有案件但无高风险 - 中风险图标
if (props.totalCases > 0) {
return new URL('../../../assets/images/report/zfx.png', import.meta.url).href
}
// 无案件 - 正常图标
return new URL('../../../assets/images/report/zq.png', import.meta.url).href
}
</script>

344
src/ui/FLXGK5D2/index.vue Normal file
View File

@@ -0,0 +1,344 @@
<template>
<div class="card shadow-sm rounded-xl overflow-hidden p-4">
<div class="border border-[#EEEEEE] rounded-xl">
<!-- 标题 -->
<div class="flex items-center mb-3 p-4">
<div class="w-8 h-8 flex items-center justify-center mr-2">
<img src="@/assets/images/report/ssfxfx.png" alt="涉诉风险整体" class="w-8 h-8 object-contain" />
</div>
<span class="font-bold text-gray-800">法院被执行人高级版</span>
</div>
<LTitle title="涉诉风险整体概览" />
<!-- 全局风险概览面板 -->
<StatisticsOverview
v-if="totalCases > 0 && lawsuitStats"
:stats="lawsuitStats"
:total-cases="totalCases"
/>
<!-- 案件类型筛选tab -->
<div v-if="totalCases > 0" class="p-4">
<van-tabs v-model:active="activeCaseTypeFilter" line-width="30px" class="lawsuit-tabs">
<!-- 全部风险 -->
<van-tab name="all">
<template #title>
<div class="flex items-center gap-1">
<span>全部风险</span>
<span>({{ caseTypeCounts.all }})</span>
</div>
</template>
</van-tab>
<!-- 各类型案件 - 使用v-for渲染 -->
<van-tab v-for="(typeInfo, type) in lawsuitTypeMap" :key="type" :name="type">
<template #title>
<div class="flex items-center gap-1">
<span>{{ typeInfo.text }}({{ caseTypeCounts[type] || 0 }})</span>
</div>
</template>
</van-tab>
</van-tabs>
<!-- 空状态展示 - 放在 tabs 外部以避免切换干扰 -->
<div v-if="filteredCases.length === 0" class="p-8 text-center text-gray-500">
<div class="flex flex-col items-center justify-center">
<van-empty :description="`暂无相关记录`" />
</div>
</div>
</div>
<!-- 案件列表 -->
<div v-if="filteredCases.length > 0" class="space-y-3 px-4 mb-4">
<div v-for="(caseItem, index) in filteredCases" :key="index" class="case-wrapper">
<!-- 案件卡片 - 可点击展开 -->
<div class="bg-white rounded-xl overflow-hidden border px-4 pt-3 border-[#DDDDDD]">
<div class="cursor-pointer relative" @click="toggleCaseExpand(caseItem.id || index, 'case', index)">
<!-- 顶部区域案件标题和案件类型 -->
<div class=" flex items-center">
<!-- 案件标题 -->
<div class="font-bold text-base text-[#333333] mr-2">{{ caseItem.ah || '暂无案号' }}</div>
<!-- 案件类型标签 -->
<span class="px-2 py-1 text-xs rounded-md font-medium bg-[#F9ECEC] text-[#EB3C3C]">
{{ getCaseTypeText(caseItem.type) }}
</span>
</div>
<!-- 中间区域立案时间 -->
<div class="pb-2">
<span class="text-sm text-[#666666]">立案</span>
<span class="text-sm text-[#333333]">{{ formatDate(caseItem.regdate) }}</span>
</div>
<!-- 底部区域风险等级 -->
<div class="flex items-center gap-2">
<!-- 风险等级标签 -->
<span class="px-2 py-1 text-xs rounded-md font-medium"
:class="getCaseTypeRiskLevel(caseItem.type).color">
{{ getCaseTypeRiskLevel(caseItem.type).text }}
</span>
<!-- 下架状态 -->
<span v-if="caseItem.sign === '0'" class="px-2 py-1 text-xs rounded-md font-medium bg-gray-100 text-gray-500">
已下架
</span>
</div>
<!-- 展开指示器 -->
<div class="absolute right-4 bottom-3 flex items-center text-xs text-gray-500">
<img src="@/assets/images/report/zk.png" alt="展开" class="w-4 h-4 container"
:class="{ 'rotate-180': isCaseExpanded(caseItem.id || index, 'case', index) }" />
</div>
</div>
<!-- 案件详情抽屉 -->
<div class="mt-4 overflow-hidden transition-all duration-300 ease-in-out" :class="{
'max-h-0 opacity-0': !isCaseExpanded(caseItem.id || index, 'case', index),
'max-h-none opacity-100': isCaseExpanded(caseItem.id || index, 'case', index),
}">
<div class="mt-1 transform transition-all duration-300">
<div class="relative">
<CaseDetail :case-data="caseItem" :case-type="caseItem.type" />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 无涉诉风险时的空状态展示 -->
<div v-else class="text-gray-500 py-10 text-center bg-gray-50 rounded-lg mx-4 mb-4">
<div class="text-gray-300 text-3xl mb-2"></div>
暂无涉诉风险记录
</div>
</div>
</div>
<!-- 温馨提示 -->
<LRemark
content="法院被执行人高级版展示申请人作为失信被执行人或限高被执行人的详细情况。数据来源于法院公开信息,包括案号、执行法院、立案时间及风险信号描述等。建议关注风险等级较高的案件及近年发生的执行记录。" />
</template>
<script setup>
import { ref, computed } from 'vue'
import LTitle from '@/components/LTitle.vue'
import LRemark from '@/components/LRemark.vue'
import StatisticsOverview from './components/StatisticsOverview.vue'
import CaseDetail from './components/CaseDetail.vue'
import { useRiskNotifier } from '@/composables/useRiskNotifier'
import {
lawsuitTypeMap,
getCaseTypeText,
getCaseTypeColor,
formatDate,
getCaseTypeRiskLevel,
parseFlatData
} from './utils/lawsuitUtils.js'
const props = defineProps({
data: {
type: Object,
required: true,
},
apiId: {
type: String,
default: '',
},
index: {
type: Number,
default: 0,
},
notifyRiskStatus: {
type: Function,
default: () => { },
},
})
// 解析扁平数据
const parsedData = computed(() => {
const data = props.data || {}
return parseFlatData(data)
})
const sx = computed(() => parsedData.value.sx)
const xg = computed(() => parsedData.value.xg)
// 用于跟踪展开的案件卡片
const expandedCases = ref({})
// 切换展开/收起案件详情
const toggleCaseExpand = (caseId, listType, index) => {
const uniqueKey = `${caseId}_${listType}_${index}`
expandedCases.value[uniqueKey] = !expandedCases.value[uniqueKey]
}
// 检查案件是否展开
const isCaseExpanded = (caseId, listType, index) => {
const uniqueKey = `${caseId}_${listType}_${index}`
return !!expandedCases.value[uniqueKey]
}
// 当前选中的案件类型筛选
const activeCaseTypeFilter = ref('all')
// 计算所有案件数据
const allCases = computed(() => {
return [...sx.value, ...xg.value]
})
// 计算总案件数
const totalCases = computed(() => allCases.value.length)
// 计算涉诉风险统计
const lawsuitStats = computed(() => {
if (totalCases.value === 0) return null
const stats = {
totalRiskItems: 0,
highRiskItems: 0,
mediumRiskItems: 0,
lowRiskItems: 0,
sxbzxrCount: sx.value.length,
xgbzxrCount: xg.value.length,
caseTypes: [],
}
// 统计规则命中数
const data = props.data || {}
Object.keys(data).forEach(key => {
if (key.startsWith('Rule_name_')) {
stats.totalRiskItems++
}
})
// 统计各类型案件数量
const typeCounts = {}
Object.keys(lawsuitTypeMap).forEach(type => {
typeCounts[type] = 0
})
allCases.value.forEach(caseItem => {
// 根据案件类型统计风险等级
const riskLevel = getCaseTypeRiskLevel(caseItem.type).level
if (riskLevel === 'high') {
stats.highRiskItems++
} else if (riskLevel === 'medium') {
stats.mediumRiskItems++
} else {
stats.lowRiskItems++
}
// 统计案件类型
if (caseItem.type) {
typeCounts[caseItem.type] = (typeCounts[caseItem.type] || 0) + 1
}
})
// 转换为数组格式
stats.caseTypes = Object.keys(typeCounts)
.filter(type => typeCounts[type] > 0)
.map(type => ({
type,
count: typeCounts[type],
name: getCaseTypeText(type),
color: getCaseTypeColor(type),
}))
.sort((a, b) => b.count - a.count)
return stats
})
// 按案件类型筛选案件
const filteredCases = computed(() => {
if (activeCaseTypeFilter.value === 'all') {
return allCases.value
}
return allCases.value.filter(caseItem => caseItem.type === activeCaseTypeFilter.value)
})
// 获取每种案件类型的数量
const caseTypeCounts = computed(() => {
const counts = {
all: totalCases.value,
}
// 初始化所有案件类型的计数
Object.keys(lawsuitTypeMap).forEach(type => {
counts[type] = 0
})
// 计算每种类型的案件数量
allCases.value.forEach(caseItem => {
if (caseItem.type) {
counts[caseItem.type]++
}
})
return counts
})
// 计算风险评分0-100分分数越高越安全
const riskScore = computed(() => {
const cases = totalCases.value;
if (cases === 0) return 100;
const data = props.data || {}
const weight = parseInt(data.Rule_final_weight) || 0;
// 如果有明确的 weight根据 weight 计算
// weight 越高风险越高,所以 score = 100 - weight
if (weight > 0) {
return Math.max(0, 100 - weight);
}
if (cases <= 2) return 70;
if (cases <= 5) return 50;
if (cases <= 10) return 30;
return 10;
});
// 使用 composable 通知父组件风险评分
useRiskNotifier(props, riskScore);
// 暴露给父组件
defineExpose({
riskScore
});
</script>
<style lang="scss" scoped>
.case-wrapper {
@apply relative;
}
.lawsuit-tabs :deep(.van-tabs__wrap) {
height: 32px !important;
background-color: transparent !important;
padding: 0 !important;
border-bottom: 1px solid #DDDDDD !important;
}
.lawsuit-tabs :deep(.van-tabs__nav) {
background-color: transparent !important;
gap: 0;
height: 32px !important;
}
.lawsuit-tabs :deep(.van-tab) {
color: #999999 !important;
font-size: 14px !important;
font-weight: 400 !important;
}
.lawsuit-tabs :deep(.van-tab--active) {
color: var(--van-theme-primary) !important;
background-color: unset !important;
}
.lawsuit-tabs :deep(.van-tabs__line) {
height: 3px !important;
border-radius: 1px !important;
}
</style>

View File

@@ -0,0 +1,143 @@
// 案件类型映射表
export const lawsuitTypeMap = {
sxbzxr: {
text: '失信被执行',
color: 'text-red-600 bg-red-50',
darkColor: 'bg-red-500',
riskLevel: 'high', // 高风险
},
xgbzxr: {
text: '限高被执行',
color: 'text-orange-600 bg-orange-50',
darkColor: 'bg-orange-500',
riskLevel: 'high', // 高风险
}
}
// 案件类型文本
export const getCaseTypeText = type => {
return lawsuitTypeMap[type]?.text || '其他案件'
}
// 案件类型颜色
export const getCaseTypeColor = type => {
return lawsuitTypeMap[type]?.color || 'text-gray-600 bg-gray-50'
}
// 案件类型深色
export const getCaseTypeDarkColor = type => {
return lawsuitTypeMap[type]?.darkColor || 'bg-gray-500'
}
// 格式化日期显示
export const formatDate = dateStr => {
if (!dateStr) return '—'
// 转换YYYY-MM-DD为年月日格式
if (dateStr.includes('-')) {
const parts = dateStr.split('-')
if (parts.length === 3) {
return `${parts[0]}${parts[1]}${parts[2]}`
}
}
return dateStr // 如果不是标准格式则返回原始字符串
}
// 获取案件状态样式
export const getCaseStatusClass = status => {
if (!status) return 'bg-gray-100 text-gray-500'
if (status.includes('已结') || status.includes('已办结')) {
return 'bg-green-50 text-green-600'
} else if (status.includes('执行中') || status.includes('审理中')) {
return 'bg-blue-50 text-blue-600'
} else if (status.includes('未执行') || status.includes('未履行')) {
return 'bg-amber-50 text-amber-600'
} else {
return 'bg-gray-100 text-gray-500'
}
}
// 根据案件类型获取风险等级
export const getCaseTypeRiskLevel = caseType => {
const typeInfo = lawsuitTypeMap[caseType]
if (!typeInfo) {
return {
level: 'low',
text: '低风险',
color: 'text-green-600 bg-green-50',
}
}
const riskLevelMap = {
high: {
text: '高风险',
color: 'text-red-600 bg-red-50',
},
medium: {
text: '中风险',
color: 'text-amber-600 bg-amber-50',
},
low: {
text: '低风险',
color: 'text-green-600 bg-green-50',
},
}
return {
level: typeInfo.riskLevel,
...riskLevelMap[typeInfo.riskLevel],
}
}
// 解析扁平数据为结构化数组
export const parseFlatData = (data) => {
const sx = [];
const xg = [];
// 最多10个
for (let i = 1; i <= 10; i++) {
// 检查失信
if (data[`el_sx${i}_casecode`]) {
sx.push({
ah: data[`el_sx${i}_casecode`],
casecode: data[`el_sx${i}_casecode`],
iname: data[`el_sx${i}_iname`],
sexname: data[`el_sx${i}_sexname`],
age: data[`el_sx${i}_age`],
courtname: data[`el_sx${i}_courtname`],
areaname: data[`el_sx${i}_areaname`],
publishdate: data[`el_sx${i}_publishdate`],
regdate: data[`el_sx${i}_regdate`],
gistcid: data[`el_sx${i}_gistcid`],
signalDesc: data[`el_sx${i}_signalDesc`],
signalRating: data[`el_sx${i}_signalRating`],
datatype: data[`el_sx${i}_datatype`],
partytypename: data[`el_sx${i}_partytypename`],
sign: data[`el_sx${i}_sign`],
type: 'sxbzxr'
});
}
// 检查限高
if (data[`el_xg${i}_casecode`]) {
xg.push({
ah: data[`el_xg${i}_casecode`],
casecode: data[`el_xg${i}_casecode`],
iname: data[`el_xg${i}_iname`],
sexname: data[`el_xg${i}_sexname`],
age: data[`el_xg${i}_age`],
courtname: data[`el_xg${i}_courtname`],
areaname: data[`el_xg${i}_areaname`],
publishdate: data[`el_xg${i}_publishdate`],
regdate: data[`el_xg${i}_regdate`],
signalDesc: data[`el_xg${i}_signalDesc`],
signalRating: data[`el_xg${i}_signalRating`],
datatype: data[`el_xg${i}_datatype`],
sign: data[`el_xg${i}_sign`],
type: 'xgbzxr'
});
}
}
return { sx, xg };
}

View File

@@ -40,10 +40,13 @@ onMounted(async () => {
}); });
const loadExampleData = async () => { const loadExampleData = async () => {
console.log('[Report.vue] 开始加载示例数据...');
try { try {
// 从 public 目录加载示例数据 // 从 public 目录加载示例数据
const response = await fetch('/example.json'); const response = await fetch('/example.json');
console.log('[Report.vue] 数据请求响应状态:', response.status);
const data = await response.json(); const data = await response.json();
console.log('[Report.vue] 原始数据条数:', Array.isArray(data) ? data.length : '非数组');
if (Array.isArray(data) && data.length > 0) { if (Array.isArray(data) && data.length > 0) {
// 直接使用任意产品类型(不影响显示) // 直接使用任意产品类型(不影响显示)
@@ -53,15 +56,18 @@ const loadExampleData = async () => {
reportData.value = data.sort((a, b) => { reportData.value = data.sort((a, b) => {
return (a.feature?.sort || 0) - (b.feature?.sort || 0); return (a.feature?.sort || 0) - (b.feature?.sort || 0);
}); });
console.log('[Report.vue] 数据排序完成,准备渲染...');
isEmpty.value = false; isEmpty.value = false;
isDone.value = true; isDone.value = true;
console.log('[Report.vue] isDone 设置为 true');
} else { } else {
console.warn('[Report.vue] 数据为空或非数组');
isEmpty.value = true; isEmpty.value = true;
isDone.value = true; isDone.value = true;
} }
} catch (error) { } catch (error) {
console.error('加载示例数据失败:', error); console.error('[Report.vue] 加载示例数据失败:', error);
isEmpty.value = true; isEmpty.value = true;
isDone.value = true; isDone.value = true;
} }